def test__hash__fail(): start = datetime.utcnow() + timedelta(0, 3) # 3 seconds from now start2 = datetime.utcnow() + timedelta(0, 4) end2 = start + timedelta(0, 2) grow3 = Grow(2, 3, 4, 5, 6, start, end2) grow4 = Grow(2, 3, 4, 5, 6, start2, end2) assert hash(grow3) != hash(grow4)
def test_to_job_id(): date_format = "%b %d %Y %H:%M:%S" start = datetime.utcnow() + timedelta(0, 3) # 3 seconds from now end2 = start + timedelta(0, 2) grow = Grow(2, 3, 4, 5, 6, start, end2) assert grow.to_job_id() == "room-2-rack-3-shelf-4-recipe-5-phase-6-start-{}-end-{}".format( start.strftime(date_format), end2.strftime(date_format) )
def test__eq__(): start = datetime.utcnow() + timedelta(0, 3) # 3 seconds from now end = start + timedelta(0, 2) # 5 seconds from now grow2 = Grow(1, 2, 3, 4, 5, start, end) grow3 = Grow(1, 2, 3, 4, 5, start, end) assert grow2 == grow3
def test__eq__fail(): start = datetime.utcnow() + timedelta(0, 3) # 3 seconds from now start2 = datetime.utcnow() + timedelta(0, 4) end = start + timedelta(0, 2) # 5 seconds from now grow3 = Grow(1, 2, 3, 4, 5, start, end) grow4 = Grow(1, 2, 3, 4, 5, start2, end) assert grow3 != grow4
def write_grow(conn, grow: Grow) -> Grow: sql = "INSERT INTO `grows` (recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" cursor = conn.cursor() cursor.execute( sql, ( grow.recipe_id, grow.start_datetime, grow.estimated_end_datetime, grow.is_finished, grow.all_fields_complete, grow.current_phase, grow.is_new_recipe, grow.tag_set, grow.nutrients, # explicitly set this to None if it's not a valid number (0 isn't valid for this field either) grow.weekly_reps if grow.weekly_reps else None, grow.pruning_date_1, grow.pruning_date_2, ), ) # return the id since it's created dynamically on insert by AUTO_INCREMENT grow_id = cursor.lastrowid grow.grow_id = grow_id cursor.close() return grow
def test_to_json(): start5 = datetime.utcnow() + timedelta(0, 4) start5 -= timedelta(microseconds=start5.microsecond) end = start5 + timedelta(0, 2) grow_to_json = Grow(1, 2, 3, 4, 5, start5, end) assert grow_to_json.to_json() == { "room_id": 1, "rack_id": 2, "shelf_id": 3, "recipe_id": 4, "recipe_phase_num": 5, "start_datetime": start5.replace( microsecond=0 ).isoformat(), "end_datetime": end.replace(microsecond=0).isoformat(), }
def test_create_grow_from_json_string_format(): start = datetime.utcnow() + timedelta(0, 3) # 3 seconds from now start -= timedelta(microseconds=start.microsecond) end = start + timedelta(0, 2) # 5 seconds from now grow = Grow.from_json( { "room_id": 1, "rack_id": 2, "shelf_id": 3, "recipe_id": 4, "recipe_phase_num": 5, "start_datetime": start.strftime("%Y-%m-%d %H:%M:%S"), "end_datetime": end.strftime("%Y-%m-%d %H:%M:%S"), } ) assert grow.room_id == 1 assert grow.rack_id == 2 assert grow.shelf_id == 3 assert grow.recipe_id == 4 assert grow.recipe_phase_num == 5 # needs to round because calendar.timegm is converting to seconds losing microsecond precision assert grow.start_datetime == start assert grow.end_datetime == end
def return_all_entities_listener(message) -> None: print("Received message in entities_listener:", message) assert "rooms" in message assert "racks" in message assert "shelves" in message assert "grows" in message assert "recipes" in message assert "recipe_phases" in message found_rooms = [Room.from_json(r) for r in message["rooms"]] found_racks = [Rack.from_json(r) for r in message["racks"]] found_shelves = [Shelf.from_json(s) for s in message["shelves"]] found_grows = [Grow.from_json(g) for g in message["grows"]] found_recipes = [Recipe.from_json(r) for r in message["recipes"]] found_recipe_phases = [ RecipePhase.from_json(rp) for rp in message["recipe_phases"] ] assert collections.Counter(found_rooms) == collections.Counter(rooms) assert collections.Counter(found_racks) == collections.Counter(racks) assert collections.Counter(found_shelves) == collections.Counter( shelves) assert collections.Counter(found_grows) == collections.Counter(grows) assert collections.Counter(found_recipes) == collections.Counter( recipes) assert collections.Counter(found_recipe_phases) == collections.Counter( recipe_phases) flag.append(True)
def return_all_entities_listener(message) -> None: message_dict = json.loads(message) print("Received message in entities_listener:", message_dict) assert "rooms" in message_dict assert "racks" in message_dict assert "shelves" in message_dict assert "grows" in message_dict assert "grow_phases" in message_dict assert "recipes" in message_dict assert "recipe_phases" in message_dict found_rooms = [Room.from_json(r) for r in message_dict["rooms"]] found_racks = [Rack.from_json(r) for r in message_dict["racks"]] found_shelves = [Shelf.from_json(s) for s in message_dict["shelves"]] found_grows = [Grow.from_json(g) for g in message_dict["grows"]] found_grow_phases = [ GrowPhase.from_json(g) for g in message_dict["grow_phases"] ] found_recipes = [Recipe.from_json(r) for r in message_dict["recipes"]] found_recipe_phases = [ RecipePhase.from_json(rp) for rp in message_dict["recipe_phases"] ] assert collections.Counter(found_rooms) == collections.Counter(rooms) assert collections.Counter(found_racks) == collections.Counter(racks) assert collections.Counter(found_shelves) == collections.Counter( shelves ) assert len(found_grows) > 0 x = len(found_grows) assert ( found_grows[x - 1].start_datetime.strftime("%Y-%m-%d %H:%M:%S") == start ) assert ( found_grows[x - 1].estimated_end_datetime.strftime( "%Y-%m-%d %H:%M:%S" ) == end ) i = len(found_grow_phases) assert ( found_grow_phases[i - 1].phase_start_datetime.strftime( "%Y-%m-%d %H:%M:%S" ) == start ) assert ( found_grow_phases[i - 1].phase_end_datetime.strftime( "%Y-%m-%d %H:%M:%S" ) == end ) j = len(found_recipes) assert found_recipe_phases[j - 1].power_level == p_level assert found_recipe_phases[j - 1].red_level == r_level assert found_recipe_phases[j - 1].blue_level == b_level flag.append(True) grow.append(found_grows[0].grow_id)
def schedule_grow_for_shelf(grow: Grow, power_level: int, red_level: int, blue_level: int) -> None: config = AppConfig( ) # no arguments needed because it's a singleton instance shelf_grow_dict = { "grow": grow.to_json(), "power_level": power_level, "red_level": red_level, "blue_level": blue_level, } config.sio.emit("set_lights_for_grow", shelf_grow_dict) print("Event emitted from socketio obj")
def read_incomplete_grows(conn) -> List[Grow]: sql = "SELECT grow_id, recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, olcc_number, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2, harvest_weight, trim_weight, dry_weight, notes FROM grows WHERE is_finished = true AND all_fields_complete = false" with conn.cursor() as cursor: cursor.execute(sql) all_grows = cursor.fetchall() print("all_grows", all_grows) found_grows: List[Grow] = [ Grow( grow_id, rid, sd, ed, fin, comp, olcc, cp, inr, ts, nts, wr, pd1, pd2, hw, tw, dw, notes, ) for ( grow_id, rid, sd, ed, fin, comp, olcc, cp, inr, ts, nts, wr, pd1, pd2, hw, tw, dw, notes, ) in all_grows ] cursor.close() return found_grows
def read_grow(conn, grow_id: int) -> Optional[Grow]: sql = "SELECT grow_id, recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, olcc_number, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2, harvest_weight, trim_weight, dry_weight, notes FROM grows WHERE grow_id = %s" with conn.cursor() as cursor: cursor.execute(sql, (grow_id)) found_grow = cursor.fetchone() grow: Optional[Grow] = None if found_grow is not None: ( grow_id, recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, olcc_number, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2, harvest_weight, trim_weight, dry_weight, notes, ) = found_grow grow = Grow( grow_id, recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, olcc_number, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2, harvest_weight, trim_weight, dry_weight, notes, ) cursor.close() return grow
def test_create_grow(): start = datetime.utcnow() + timedelta(0, 3) # 3 seconds from now start -= timedelta(microseconds=start.microsecond) end = start + timedelta(0, 2) # 5 seconds from now grow = Grow(1, 2, 3, 4, 5, start, end) assert grow.room_id == 1 assert grow.rack_id == 2 assert grow.shelf_id == 3 assert grow.recipe_id == 4 assert grow.recipe_phase_num == 5 assert grow.start_datetime == start assert grow.end_datetime == end
def _test_send_shelf_grow(sio, room_id, rack_id, shelf_id, recipe_phases): assert len(recipe_phases) == 1 # only support 1 phase for now print("Sending shelf grow") start = datetime.utcnow() + timedelta(0, 3) # 3 seconds from now end = start + timedelta(0, 2) # 5 seconds from now start_time = start.strftime("%Y-%m-%d %H:%M:%S") end_time = end.strftime("%Y-%m-%d %H:%M:%S") shelf_grows = [] for rp in recipe_phases: shelf_grow = Grow.from_json({ "room_id": room_id, "rack_id": rack_id, "shelf_id": shelf_id, "recipe_id": rp.recipe_id, "recipe_phase_num": rp.recipe_phase_num, "start_datetime": start_time, "end_datetime": end_time, }) shelf_grows.append(shelf_grow) flag = [] @sio.on("start_grows_for_shelves_succeeded") def start_grows_for_shelves_succeeded(message) -> None: assert "succeeded" in message assert message["succeeded"] flag.append(True) @sio.on("set_lights_for_grow") def set_lights_for_grow(message) -> None: assert "grow" in message assert "power_level" in message assert "red_level" in message assert "blue_level" in message flag.append(True) sio.emit("start_grows_for_shelves", {"grows": [s.to_json() for s in shelf_grows]}) wait_for_event(sio, flag, 1, 5, "test_start_grows_for_shelves") for i in range(len(shelf_grows)): wait_for_event(sio, flag, i + 2, 5, "test_set_lights_for_grow_job") print("test send shelf grow passed") return shelf_grow
def read_shelf_current_grows(conn, shelf_id) -> List[Grow]: sql = "SELECT room_id, rack_id, shelf_id, recipe_id, recipe_phase_num, start_datetime, end_datetime FROM grows WHERE shelf_id=%s AND end_datetime > %s" utc_now = datetime.utcnow() with conn.cursor() as cursor: cursor.execute(sql, (shelf_id, utc_now)) all_grows = cursor.fetchall() print("all_grows", all_grows) found_shelf_grows: List[Grow] = [ Grow(room_id, rack_id, sid, rid, rpn, sd, ed) for (room_id, rack_id, sid, rid, rpn, sd, ed) in all_grows ] cursor.close() return found_shelf_grows
def start_grows_for_shelves(message) -> None: print("message:", message) logging.debug("message sent to post_room_schedule:", message) if "grows" not in message: send_message_to_namespace_if_specified( socketio, message, "start_grow_for_shelf_succeeded", { "succeeded": False, "reason": "Grow not included" }, ) grows: List[Grow] = [Grow.from_json(g) for g in message["grows"]] for grow in grows: ( power_level, red_level, blue_level, ) = app_config.db.read_lights_from_recipe_phase( grow.recipe_id, grow.recipe_phase_num) app_config.scheduler.add_job( schedule_grow_for_shelf, "interval", start_date=grow.start_datetime, end_date=grow.end_datetime, args=[grow, power_level, red_level, blue_level], id=grow.to_job_id(), minutes=5, ) # TODO (lww515): What to do after harvest is finished? # write grows to db app_config.db.write_grows(grows) logging.debug("start_grow_for_shelf succeeded!") send_message_to_namespace_if_specified( socketio, message, "start_grows_for_shelves_succeeded", {"succeeded": True}) print("Grow started successfully, event emitted")
def test__str__(): start6 = datetime.utcnow() + timedelta(0, 4) end = start6 + timedelta(0, 2) grow = Grow(1, 2, 3, 4, 5, start6, end) grow2 = Grow(1, 2, 3, 4, 5, start6, end) assert str(grow2) == str(grow)
def start_grows_for_shelves(message) -> None: db_conn = None try: validation_succeeded, failure_reason = validate_start_grows_for_shelves( app_config, message) print( "Start_grows_for_shelves validation:", validation_succeeded, failure_reason, ) if not validation_succeeded: send_message_to_namespace_if_specified( socketio, message, "start_grows_for_shelves_succeeded", { "succeeded": False, "reason": failure_reason }, ) print("Failed validation!") return db_conn = app_config.db._new_transaction() is_new_recipe: bool = bool(message["is_new_recipe"]) recipe_id: Optional[int] = None end_date_str: Optional[str] = None end_date: Optional[datetime] = None if is_new_recipe: # create the recipe and the recipe phases before creating the grow recipe_name = (message["recipe_name"] if "recipe_name" in message else None) recipe_no_id: Recipe = Recipe(None, recipe_name) recipe: Recipe = app_config.db.write_recipe( db_conn, recipe_no_id) recipe_id = recipe.recipe_id light_configurations: List[Any] = message["grow_phases"] end_date_str = message["end_date"] end_date = iso8601_string_to_datetime(end_date_str) recipe_phases: List[ RecipePhase] = create_recipe_phases_from_light_configurations( light_configurations, recipe_id, end_date) print("recipe_phases:", recipe_phases) # write the recipe phases to database app_config.db.write_recipe_phases(db_conn, recipe_phases) else: recipe_id = message["template_recipe_id"] # create the grow first so we can read the grow_id grow_start_date: datetime = iso8601_string_to_datetime( message["grow_phases"][0]["start_date"]) grow_estimated_end_date: datetime = iso8601_string_to_datetime( message["end_date"]) current_phase: int = 0 # tag set, nutrients, weekly reps, and pruning dates are optional fields tag_set: Optional[str] = message.get("tag_set") nutrients: Optional[str] = message.get("nutrients") weekly_reps: Optional[int] = message.get("weekly_reps") pruning_date_1: Optional[datetime] = iso8601_string_to_datetime( message["pruning_date_1"]) if message.get( "pruning_date_1") else None pruning_date_2: Optional[datetime] = iso8601_string_to_datetime( message["pruning_date_2"]) if message.get( "pruning_date_2") else None grow_without_id: Grow = Grow( None, recipe_id, grow_start_date, grow_estimated_end_date, False, False, None, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2, None, None, None, None, ) grow: Grow = app_config.db.write_grow(db_conn, grow_without_id) light_configurations = message["grow_phases"] end_date_str = message["end_date"] end_date = iso8601_string_to_datetime(end_date_str) grow_phases: List[ GrowPhase] = create_grow_phases_from_light_configurations( light_configurations, grow.grow_id, recipe_id, end_date) shelf_grows: List[ShelfGrow] = [] for shelf_data in message["shelves"]: shelf_data["grow_id"] = grow.grow_id shelf_grow = ShelfGrow.from_json(shelf_data) shelf_grows.append(shelf_grow) # only schedule 1st grow phase, but write all grow phases to DB first_grow_phase: GrowPhase = grow_phases[0] ( power_level, red_level, blue_level, ) = app_config.db.read_lights_from_recipe_phase( db_conn, first_grow_phase.recipe_id, first_grow_phase.recipe_phase_num, ) # write grow phases and shelf grows to db app_config.db.write_grow_phases(db_conn, grow_phases) app_config.db.write_shelf_grows(db_conn, shelf_grows) # enqueue the job to the job scheduler client_schedule_job( shelf_grows, first_grow_phase, power_level, red_level, blue_level, ) print("start_grow_for_shelf succeeded!") # commit the transaction db_conn.commit() send_message_to_namespace_if_specified( socketio, message, "start_grows_for_shelves_succeeded", { "succeeded": True, "grow": grow.to_json() }, ) print("Grow started successfully, event emitted") except Exception as e: exception_str: str = str(e) print( "Error with starting grow:", message, exception_str, traceback.format_exc(), ) # rollback connection if it exists and report error back to client if db_conn: db_conn.rollback() send_message_to_namespace_if_specified( socketio, message, "start_grows_for_shelves_succeeded", { "succeeded": False, "reason": "Unexpected error. Please document the conditions that lead to this error. {}" .format(exception_str), }, ) finally: if db_conn: # close the db connection if it's defined db_conn.close()
def harvest_grow(message) -> None: db_conn = None try: if "grow" not in message: send_message_to_namespace_if_specified( socketio, message, "harvest_grow_response", { "succeeded": False, "reason": "Grow not included" }, ) return grow_json = message["grow"] grow_id: int = int(grow_json["grow_id"]) grow: Optional[Grow] = app_config.db.read_grow(grow_id) if not grow: send_message_to_namespace_if_specified( socketio, message, "harvest_grow_response", { "succeeded": False, "reason": "Grow not found" }, ) return found_grow_json = grow.to_json() # read all data sent in by user, and mark it on grow for key in grow_json: found_grow_json[key] = grow_json[key] updated_grow: Grow = Grow.from_json(found_grow_json) # harvest the grow by marking it as complete harvest_datetime: datetime = datetime.utcnow() updated_grow.estimated_end_datetime = harvest_datetime updated_grow.is_finished = True # end grow db_conn = app_config.db._new_transaction() app_config.db.update_grow_harvest_data(db_conn, updated_grow) # read current grow phase current_phase: int = updated_grow.current_phase current_grow_phase: Optional[ GrowPhase] = app_config.db.read_grow_phase( db_conn, updated_grow.grow_id, current_phase) if not current_grow_phase: send_message_to_namespace_if_specified( socketio, message, "harvest_grow_response", { "succeeded": False, "reason": "Current grow phase {} not found".format( current_phase), }, ) return # remove ongoing job so that it stops running client_remove_job(current_grow_phase) # update current recipe phase to have proper end date print("Updating grow_phase:", current_grow_phase) app_config.db.end_grow_phase(db_conn, current_grow_phase, harvest_datetime) # commit the transaction db_conn.commit() send_message_to_namespace_if_specified(socketio, message, "harvest_grow_response", {"succeeded": True}) except Exception as e: exception_str: str = str(e) print( "Error with harvesting grow:", message, exception_str, traceback.format_exc(), ) # rollback connection if it exists and report error back to client if db_conn: db_conn.rollback() send_message_to_namespace_if_specified( socketio, message, "harvest_grow_response", { "succeeded": False, "reason": "Unexpected error. Please document the conditions that lead to this error. {}" .format(exception_str), }, ) finally: if db_conn: # close the db connection if it's defined db_conn.close()
def update_incomplete_grow(message) -> None: db_conn = None try: if "grow" not in message: send_message_to_namespace_if_specified( socketio, message, "update_incomplete_grow_response", { "succeeded": False, "reason": "Grow not included" }, ) return grow_json = message["grow"] grow_id: int = int(grow_json["grow_id"]) grow: Optional[Grow] = app_config.db.read_grow(grow_id) if not grow: send_message_to_namespace_if_specified( socketio, message, "update_incomplete_grow_response", { "succeeded": False, "reason": "Grow not found" }, ) return found_grow_json = grow.to_json() # read all data sent in by user, and mark it on grow for key in grow_json: found_grow_json[key] = grow_json[key] updated_grow: Grow = Grow.from_json(found_grow_json) # harvest the grow by marking it as complete harvest_datetime: datetime = datetime.utcnow() updated_grow.estimated_end_datetime = harvest_datetime updated_grow.is_finished = True print( "grow_json to harvest in update_incomplete_grow:", grow_json, updated_grow, flush=True, ) # end grow db_conn = app_config.db._new_transaction() app_config.db.update_grow_harvest_data(db_conn, updated_grow) # commit changes db_conn.commit() send_message_to_namespace_if_specified( socketio, message, "update_incomplete_grow_response", {"succeeded": True}, ) except Exception as e: exception_str: str = str(e) print( "Error with updating incomplete grow:", message, exception_str, traceback.format_exc(), ) # rollback connection if it exists and report error back to client if db_conn: db_conn.rollback() send_message_to_namespace_if_specified( socketio, message, "update_incomplete_grow_response", { "succeeded": False, "reason": "Unexpected error. Please document the conditions that lead to this error. {}" .format(exception_str), }, ) finally: if db_conn: # close the db connection if it's defined db_conn.close()