def register_device(): metadata = request.get_json() device_id = metadata.pop('id') if device_id is None: return jsonify(success=False, error_code="MISSING_DEVICE_ID"), 400 if not isinstance(device_id, int): return jsonify(success=False, error_code="DEVICE_ID_NOT_AN_INTEGER"), 400 # check if the device is already registered if db.get_device(device_id=device_id): return jsonify(success=False, error_code="DEVICE_ALREADY_REGISTERED"), 400 smart_plug_key = metadata.pop("smart_plug_key") if not smart_plug_key: return jsonify(success=False, error_code="MISSING_DEVICE_SMART_PLUG_KEY"), 400 db.create_device(device_id=device_id, smart_plug_key=smart_plug_key, metadata=metadata) return jsonify(success=True, device_id=device_id)
def test_connect(): device_id = request.args.get("device_id", type=int) device = db.get_device(device_id=device_id) if not device: # Unknown device tried to connect print("Rejecting unknown device connection") return False print(f"Device id={device_id} has connected")
def job_update_status(job_id): body = request.get_json() assert "device_id" in body assert "status" in body device_id = body['device_id'] status = body['status'] result = body.get("result") device = db.get_device(device_id) if not device: return jsonify(success=False, error_code="INVALID_DEVICE_ID"), 400 job = db.get_job(job_id) if not job: return jsonify(success=False, error_code="INVALID_JOB_ID"), 400 if not job.assigned_device: return jsonify(success=False, error_code="CANNOT_UPDATE_UNASSIGNED_JOB") if job.assigned_device.id != device_id: return jsonify(success=False, error_code="JOB_ASSIGNED_TO_ANOTHER_DEVICE"), 400 job.status = status if status == db.Job.SUCCEEDED or status == db.Job.FAILED: # This job has finished, so it's no longer assigned to a device job.assigned_device = None if result: print(f"\n\n[JOB RESULT] Received output for job id {job_id}:") print(result, "\n\n") job.save() return jsonify(success=True)
def device_heartbeat(device_id): device = db.get_device(device_id=device_id) if not device: return jsonify(success=False, error_code="DEVICE_NOT_FOUND"), 400 # metadata stores a json object of arbitrary device related data (such as battery life, cpu mem, etc.) device.metadata = request.get_json() device.update_metadata_history() device.last_heartbeat = datetime.utcnow() device.save() # TODO: need to add charging logic based on phone's battery level on heartbeat # possibly something like phone.metadata.charge < 20 -> phone.start_charging() ... if device.needs_to_start_charging() and not device.decommissioned: device.start_charging() elif device.needs_to_stop_charging() and not device.decommissioned: device.stop_charging() elif device.decommissioned: # if the device is behaving badly, stop charging it. device.stop_charging() return jsonify(success=True)
def check_jobs(self): if self.stopped: # stops recursively generating threads. return while not self.stopped: print("[CHECKING JOBS]") jobs = db.get_all_jobs() # ==== for each job ==== for job in jobs: job_json = job.to_json() job_max_secs = job_json["resource_requirements"][ "max_runtime_secs"] # Check if job timed out, if so then try to cancel it and reschedule it. if job_max_secs > 0 and job_json[ "status"] == job.ASSIGNED and job_json[ "assigned_device_id"] is not None: time_updated = datetime.strptime(job_json["time_updated"], '%Y-%m-%d %H:%M:%S.%f') timeout_datetime = timedelta( seconds=job_max_secs) + time_updated if timeout_datetime < datetime.utcnow(): # update device's num_failed_jobs device = db.get_device(job_json["assigned_device_id"]) device.num_failed_jobs += 1 device.save() print("[JOB TIMEOUT]: Device", device.id, "on Job", job.id) self.cancel_and_reschedule_job(job.id) # For unassigned jobs. if job_json["status"] == job.UNASSIGNED: # Check if the phone has not acknowledged the job for 10 seconds. If so, then increase the num_failed_acks and reschedule the job time_updated = datetime.strptime(job_json["time_updated"], '%Y-%m-%d %H:%M:%S.%f') timeout_datetime = timedelta( seconds=ACK_TIMEOUT) + time_updated if timeout_datetime < datetime.utcnow( ) and job.id in self.pending_job_acks: # update device's num_failed_acks device = db.get_device(self.pending_job_acks[job.id]) device.num_failed_acks += 1 device.save() print("[NO DEVICE ACK]: Device", device.id, "on Job", job.id) self.remove_pending_acknowledgement(job.id) self.cancel_and_reschedule_job(job.id) # For jobs that are just unassigned in general, hence there is no pending ack's: elif job.id not in self.pending_job_acks and job.can_be_retried: schedule_job(job) # For failed jobs: retry them. if job_json["status"] == job.FAILED and job.can_be_retried: schedule_job(job) # ==== END of "for each job" section ==== # Start another thread in a few seconds # threading.Timer(CHECK_JOBS_INTERVAL_SEC, self.check_jobs).start() time.sleep(CHECK_JOBS_INTERVAL_SEC)