def pneumatrol_1_resume_job(): timestamp = datetime.now().timestamp() # Get the reason for the pause. The app will return the short_description of the activity code if "downtime_reason" not in request.json: return json.dumps({"success": False}) notes = request.json.get("notes", "") downtime_reason = request.json["downtime_reason"] # Get the activity code corresponding to the description activity_code = ActivityCode.query.filter_by(short_description=downtime_reason).first() user_session = UserSession.query.filter_by(device_ip=request.remote_addr, active=True).first() if user_session is None: return json.dumps({"success": False, "reason": "User is logged out"}) current_job = Job.query.filter_by(user_session_id=user_session.id, active=True).first() time = datetime.now().strftime("%H:%M") if not current_job.notes: current_job.notes = "" # Initialise the string if null if notes != "": current_job.notes += f"{time} - {downtime_reason} - {notes} \n" # Mark the most recent activity in the database as complete complete_last_activity(machine_id=user_session.machine_id, activity_code_id=activity_code.id, timestamp_end=timestamp) # Start a new activity new_activity = Activity(machine_id=user_session.machine_id, machine_state=Config.MACHINE_STATE_RUNNING, activity_code_id=Config.UPTIME_CODE_ID, user_id=user_session.user_id, job_id=current_job.id, timestamp_start=timestamp) db.session.add(new_activity) db.session.commit() return json.dumps({"success": True})
def end_user_sessions(user_id=None, machine_id=None): """ End all sessions for a user or a machine (Either can be given)""" timestamp = datetime.now().timestamp() sessions = [] if user_id: sessions.extend( UserSession.query.filter_by(user_id=user_id, active=True).all()) elif machine_id: sessions.extend( UserSession.query.filter_by(machine_id=machine_id, active=True).all()) else: sessions.extend(UserSession.query.filter_by(active=True).all()) for us in sessions: current_app.logger.info(f"Ending user session {us}") us.timestamp_logout = timestamp us.active = False # End all jobs assigned to the session for job in us.jobs: job.end_time = timestamp job.active = None db.session.commit( ) # Not committing here would sometimes cause sqlite to have too many operations # Set the activity to "no user" complete_last_activity(machine_id=us.machine.id, timestamp_end=timestamp) new_activity = Activity(machine_id=us.machine.id, timestamp_start=timestamp, activity_code_id=Config.NO_USER_CODE_ID, machine_state=Config.MACHINE_STATE_OFF) current_app.logger.debug( f"Starting {new_activity} on logout of {us.user}") db.session.add(new_activity) db.session.commit()
def pneumatrol_1_start_job_or_setting(setting): if not request.is_json: return 404 timestamp = datetime.now().timestamp() user_session = UserSession.query.filter_by(device_ip=request.remote_addr, active=True).first() if user_session is None: return json.dumps({"success": False, "reason": "User is logged out"}) try: machine = user_session.machine except: return json.dumps({"success": False, "reason": "Server did not receive correct data"}) # Create the job if setting: try: machine = user_session.machine except: return json.dumps({"success": False, "reason": "Server did not receive correct data"}) job = Job(start_time=timestamp, user_id=user_session.user_id, user_session_id=user_session.id, wo_number=request.json["wo_number"], planned_set_time=request.json["planned_set_time"], machine_id=machine.id, active=True) else: job = Job(start_time=timestamp, user_id=user_session.user_id, user_session_id=user_session.id, wo_number=request.json["wo_number"], planned_run_time=request.json["planned_run_time"], planned_quantity=request.json["planned_quantity"], planned_cycle_time=request.json["planned_cycle_time"], machine_id=machine.id, active=True) db.session.add(job) db.session.commit() # End the current activity complete_last_activity(machine_id=machine.id, timestamp_end=timestamp) # Set the first activity depending on whether the machine is being set if setting: starting_activity_code = Config.SETTING_CODE_ID else: starting_activity_code = Config.UPTIME_CODE_ID # Start a new activity new_activity = Activity(machine_id=machine.id, machine_state=Config.MACHINE_STATE_RUNNING, activity_code_id=starting_activity_code, user_id=user_session.user_id, job_id=job.id, timestamp_start=timestamp) db.session.add(new_activity) db.session.commit() current_app.logger.info(f"{user_session.user} started {job}") return json.dumps({"success": True})
def change_activity(machine, job, user): current_app.logger.debug(f"changing activity") complete_last_activity(machine_id=machine.id) chance_the_activity_is_uptime = 0.8 if random.random() < chance_the_activity_is_uptime: new_activity = Activity(machine_id=machine.id, timestamp_start=datetime.now().timestamp(), machine_state=1, activity_code_id=Config.UPTIME_CODE_ID, job_id=job.id, user_id=user.id) else: # otherwise the activity is downtime new_activity = Activity(machine_id=machine.id, timestamp_start=datetime.now().timestamp(), machine_state=0, activity_code_id=randrange( 2, len(ActivityCode.query.all())), job_id=job.id, user_id=user.id) db.session.add(new_activity) db.session.commit()
def pneumatrol_end_job_or_setting(setting): timestamp = datetime.now().timestamp() user_session = UserSession.query.filter_by(device_ip=request.remote_addr, active=True).first() if user_session is None: return json.dumps({"success": False, "reason": "User is logged out"}) # End the current job current_job = Job.query.filter_by(user_session_id=user_session.id, active=True).first() current_job.end_time = timestamp current_job.active = None if setting: try: scrap_quantity = int(request.json["scrap_quantity"]) except KeyError: return json.dumps({"success": False, "reason": "Did not receive correct data from server"}) current_job.setup_scrap = scrap_quantity else: try: actual_quantity = int(request.json["actual_quantity"]) scrap_quantity = int(request.json["scrap_quantity"]) except KeyError: return json.dumps({"success": False, "reason": "Did not receive correct data from server"}) current_job.production_scrap = scrap_quantity current_job.actual_quantity = actual_quantity db.session.commit() # Mark the most recent activity in the database as complete complete_last_activity(machine_id=user_session.machine_id, timestamp_end=timestamp) # Start a new activity new_activity = Activity(machine_id=user_session.machine_id, machine_state=Config.MACHINE_STATE_OFF, activity_code_id=Config.UNEXPLAINED_DOWNTIME_CODE_ID, user_id=user_session.user_id, timestamp_start=timestamp) db.session.add(new_activity) db.session.commit() current_app.logger.debug(f"User {user_session.user} ended {current_job}") return json.dumps({"success": True})
def create_multiple_machines_gantt(graph_start, graph_end, machine_ids): """ Creates a gantt plot of OEE for all machines in the database between given times graph_start = the start time of the graph graph_end = the end time of the graph machine_ids = a list of ids to include in the graph""" activities = [] machine_ids.sort() for machine_id in machine_ids: machine_activities = get_machine_activities(machine_id=machine_id, timestamp_start=graph_start, timestamp_end=graph_end) # If a machine has no activities, add a fake one so it still shows on the graph if len(machine_activities) == 0: activities.append(Activity(timestamp_start=graph_start, timestamp_end=graph_start)) else: activities.extend(machine_activities) df = get_activities_df(activities=activities, group_by="machine_name", graph_start=graph_start, graph_end=graph_end, crop_overflow=True) if len(df) == 0: return "No machine activity" # Create the colours dictionary using codes' colours from the database colours = {} for act_code in ActivityCode.query.all(): colours[act_code.short_description] = act_code.graph_colour fig = ff.create_gantt(df, group_tasks=True, colors=colours, index_col='Code', bar_width=0.4) # Create a layout object using the layout automatically created layout = Layout(fig['layout']) layout = apply_default_layout(layout) layout.showlegend = True fig['layout'] = layout config = {'responsive': True} return plot(fig, output_type="div", include_plotlyjs=True, config=config)
def pneumatrol_1_pause_job(): timestamp = datetime.now().timestamp() user_session = UserSession.query.filter_by(device_ip=request.remote_addr, active=True).first() if user_session is None: return json.dumps({"success": False, "reason": "User is logged out"}) current_job = Job.query.filter_by(user_session_id=user_session.id, active=True).first() # Mark the most recent activity in the database as complete complete_last_activity(machine_id=user_session.machine_id, timestamp_end=timestamp) # Start a new activity new_activity = Activity(machine_id=user_session.machine_id, machine_state=Config.MACHINE_STATE_OFF, activity_code_id=Config.UNEXPLAINED_DOWNTIME_CODE_ID, user_id=user_session.user_id, job_id=current_job.id, timestamp_start=timestamp) db.session.add(new_activity) db.session.commit() return json.dumps({"success": True})
def android_start_job(): if not request.is_json: return 404 timestamp = datetime.now().timestamp() user_session = UserSession.query.filter_by(device_ip=request.remote_addr, active=True).first() machine = user_session.machine if user_session.user.has_job(): return 400 # Create the job job = Job(start_time=timestamp, user_id=user_session.user_id, user_session_id=user_session.id, wo_number=request.json["wo_number"], planned_run_time=request.json["planned_run_time"], planned_quantity=request.json["planned_quantity"], machine_id=machine.id, active=True) db.session.add(job) db.session.commit() # End the current activity complete_last_activity(machine_id=machine.id, timestamp_end=timestamp) # Set the first activity starting_activity_code = Config.UPTIME_CODE_ID # Start a new activity new_activity = Activity(machine_id=machine.id, machine_state=Config.MACHINE_STATE_RUNNING, activity_code_id=starting_activity_code, user_id=user_session.user_id, job_id=job.id, timestamp_start=timestamp) db.session.add(new_activity) db.session.commit() current_app.logger.info(f"{user_session.user} started {job}") return json.dumps({"success": True})
def android_end_job(): timestamp = datetime.now().timestamp() user_session = UserSession.query.filter_by(device_ip=request.remote_addr, active=True).first() try: actual_quantity = int(request.json["actual_quantity"]) except KeyError: current_app.logger.error( f"Received incorrect data from {user_session} while ending job") return json.dumps({ "success": False, "reason": "Server error parsing data" }) # End the current job current_job = Job.query.filter_by(user_session_id=user_session.id, active=True).first() current_job.end_time = timestamp current_job.active = None current_job.actual_quantity = actual_quantity db.session.commit() # Mark the most recent activity in the database as complete complete_last_activity(machine_id=user_session.machine_id, timestamp_end=timestamp) # Start a new activity new_activity = Activity( machine_id=user_session.machine_id, machine_state=Config.MACHINE_STATE_OFF, activity_code_id=Config.UNEXPLAINED_DOWNTIME_CODE_ID, user_id=user_session.user_id, timestamp_start=timestamp) db.session.add(new_activity) db.session.commit() current_app.logger.debug(f"User {user_session.user} ended {current_job}") return json.dumps({"success": True})
def start_user_session(user_id, device_ip): """ Start a new session. Usually called when a user logs in. Fails if no machine is assigned to the device ip""" timestamp = datetime.now().timestamp() user_session = UserSession.query.filter_by(user_id=user_id, device_ip=device_ip, active=True).first() # Close any user sessions that the current user has if user_session is not None: current_app.logger.warning( f"Tried to start a user session for user {user_id} while one is already open. Closing..." ) end_user_sessions(user_id) machine = Machine.query.filter_by(device_ip=device_ip).first() if machine is None: current_app.logger.info(f"No machine assigned to {device_ip}") return False # Close any sessions that exist on the current machine end_user_sessions(machine_id=machine.id) # Create the new user session new_us = UserSession(user_id=user_id, machine_id=machine.id, device_ip=device_ip, timestamp_login=timestamp, active=True) db.session.add(new_us) # Change the machine activity now that the user is logged in complete_last_activity(machine_id=machine.id, timestamp_end=timestamp) new_activity = Activity( machine_id=machine.id, timestamp_start=timestamp, activity_code_id=Config.UNEXPLAINED_DOWNTIME_CODE_ID, machine_state=Config.MACHINE_STATE_OFF) db.session.add(new_activity) db.session.commit() current_app.logger.info(f"Started user session {new_us}") return True
def android_update_activity(): timestamp = datetime.now().timestamp() user_session = UserSession.query.filter_by(device_ip=request.remote_addr, active=True).first() try: selected_activity_description = request.json["selected_activity_code"] except KeyError: return json.dumps({"success": False}) # Mark the most recent activity in the database as complete complete_last_activity(machine_id=user_session.machine_id, timestamp_end=timestamp) # Start a new activity # The current job is the only active job belonging to the user session current_job = Job.query.filter_by(user_session_id=user_session.id, active=True).first() # The activity code is obtained from the request activity_code = ActivityCode.query.filter_by( short_description=selected_activity_description).first() # The machine state is calculated from the activity code if activity_code.id == Config.UPTIME_CODE_ID: machine_state = Config.MACHINE_STATE_RUNNING else: machine_state = Config.MACHINE_STATE_OFF # Create the new activity new_activity = Activity(machine_id=user_session.machine_id, machine_state=machine_state, activity_code_id=activity_code.id, user_id=user_session.user_id, job_id=current_job.id, timestamp_start=timestamp) db.session.add(new_activity) db.session.commit() current_app.logger.debug(f"Started {new_activity} for {current_job}") return json.dumps({"success": True, "colour": activity_code.graph_colour})
def edit_machine(): """The page to edit a machine (also used for creating a new machine) This page will allow an ID to be specified for new machines being created, but will not allow the ID to be changed for existing machines.""" form = MachineForm() ids = [] # Get a list of schedules to create form dropdown schedules = [] for s in Schedule.query.all(): schedules.append((str(s.id), str(s.name))) # The ID has to be a string to match the data returned from client # Add the list of schedules to the form form.schedule.choices = schedules # Create machine group dropdown groups = [] groups.append(("0", str("No group"))) for g in MachineGroup.query.all(): groups.append((str(g.id), str(g.name))) form.group.choices = groups # Get a list of workflow types to create dropdown workflow_types = [] for t in WorkflowType.query.all(): workflow_types.append((str(t.id), str(t.name))) # Add the list of workflow types to the form form.workflow_type.choices = workflow_types # If new=true then the request is for a new machine to be created if 'new' in request.args and request.args['new'] == "True": creating_new_machine = True else: creating_new_machine = False if creating_new_machine: # Create a new machine machine = Machine(name="", active=True) # Add and flush now to retrieve an id for the new entry db.session.add(machine) db.session.flush() message = "Create new machine" # Create a list of existing machine IDs to pass to data validation for m in Machine.query.all(): ids.append(m.id) # Allow it to accept the id that has just been assigned for the new entry if machine.id in ids: ids.remove(machine.id) # Otherwise get the machine to be edited elif 'machine_id' in request.args: # Prevent the ID from being edited for existing machines form.id.render_kw = {'readonly': True} try: machine_id = int(request.args['machine_id']) machine = Machine.query.get_or_404(machine_id) except ValueError: current_app.logger.warn(f"Error parsing machine_id in URL. " f"machine_id provided : {request.args['machine_id']}") error_message = f"Error parsing machine_id : {request.args['machine_id']}" return abort(400, error_message) # Show a warning to the user message = f"This machine ID ({machine_id}) can only be set when a new machine is being created. " \ "If the machine is no longer needed, deselect \"Active\" for this machine to hide it from the users." else: error_message = "No machine_id specified" current_app.logger.warn("No machine_id specified in URL") return abort(400, error_message) # Create validators for the form # Create a list of existing names and IPs to prevent duplicates being entered names = [] ips = [] for m in Machine.query.all(): names.append(str(m.name)) ips.append(str(m.device_ip)) # Don't prevent saving with its own current name/IP if machine.name in names: names.remove(machine.name) if machine.device_ip in ips: ips.remove(machine.device_ip) # Don't allow duplicate machine names form.name.validators = [NoneOf(names, message="Name already exists"), DataRequired()] # Don't allow duplicate device IPs form.device_ip.validators = [NoneOf(ips, message="This device is already assigned to a machine"), DataRequired()] # Don't allow duplicate IDs form.id.validators = [NoneOf(ids, message="A machine with that ID already exists"), DataRequired()] if form.validate_on_submit(): current_app.logger.info(f"{machine} edited by {current_user}") # Save the new values on submit machine.name = form.name.data machine.active = form.active.data machine.workflow_type_id = form.workflow_type.data machine.schedule_id = form.schedule.data # If no machine group is selected, null the column instead of 0 if form.group.data == '0': machine.group_id = None else: machine.group_id = form.group.data # Save empty ip values as null to avoid unique constraint errors in the database if form.device_ip.data == "": machine.device_ip = None else: machine.device_ip = form.device_ip.data # If creating a new machine, save the ID and start an activity on the machine if creating_new_machine: machine.id = form.id.data current_app.logger.info(f"{machine} created by {current_user}") first_act = Activity(machine_id=machine.id, timestamp_start=datetime.now().timestamp(), machine_state=Config.MACHINE_STATE_OFF, activity_code_id=Config.NO_USER_CODE_ID) db.session.add(first_act) current_app.logger.debug(f"{first_act} started on machine creation") try: db.session.add(machine) db.session.commit() except IntegrityError as e: return str(e) return redirect(url_for('admin.admin_home')) # Fill out the form with existing values to display on the page form.id.data = machine.id form.active.data = machine.active form.schedule.data = str(machine.schedule_id) form.group.data = str(machine.group_id) form.workflow_type.data = str(machine.workflow_type_id) if not creating_new_machine: form.name.data = machine.name form.device_ip.data = machine.device_ip return render_template("admin/edit_machine.html", form=form, message=message)
def machine_activity(): """ Receives JSON data detailing a machine's activity and saves it to the database Assigns a simple activity code depending on machine state Example format: { "machine_id": 1, "machine_state": 1, "timestamp_start": 1500000000, "timestamp_end": 1500000000 } """ # Return an error if the request is not in json format if not request.is_json: response = jsonify({"error": "Request is not in json format"}) response.status_code = 400 return response data = request.get_json() # I was getting an issue with get_json() sometimes returning a string and sometimes dict so I did this if isinstance(data, str): data = json.loads(data) # Get all of the arguments, respond with an error if not provided if 'machine_id' not in data: response = jsonify({"error": "No machine_id provided"}) response.status_code = 400 return response machine = Machine.query.get(data['machine_id']) if machine is None: response = jsonify({ "error": "Could not find machine with ID " + str(data['machine_id']) }) response.status_code = 400 return response if 'machine_state' not in data: response = jsonify({"error": "No machine_state provided"}) response.status_code = 400 return response try: machine_state = int(data['machine_state']) except ValueError: response = jsonify({"error": "Could not understand machine_state"}) response.status_code = 400 return response if 'timestamp_start' not in data: response = jsonify({"error": "No timestamp_start provided"}) response.status_code = 400 return response timestamp_start = data['timestamp_start'] if 'timestamp_end' not in data: response = jsonify({"error": "No timestamp_end provided"}) response.status_code = 400 return response timestamp_end = data['timestamp_end'] if not validate_timestamp(timestamp_start) or not validate_timestamp( timestamp_end): response = jsonify({"error": "Bad timestamp"}) response.status_code = 400 return response if int(machine_state) == Config.MACHINE_STATE_RUNNING: activity_id = Config.UPTIME_CODE_ID else: activity_id = Config.UNEXPLAINED_DOWNTIME_CODE_ID # Create and save the activity new_activity = Activity(machine_id=machine.id, machine_state=machine_state, activity_code_id=activity_id, timestamp_start=timestamp_start, timestamp_end=timestamp_end) db.session.add(new_activity) db.session.commit() # Recreate the data and send it back to the client for confirmation response = jsonify({ "machine_id": machine.id, "machine_state": new_activity.machine_state, "timestamp_start": new_activity.timestamp_start, "timestamp_end": new_activity.timestamp_end }) response.status_code = 201 return response
def setup_database(): """ Enter default values into the database on its first run""" db.create_all() create_default_users() if len(ActivityCode.query.all()) == 0: no_user_code = ActivityCode(id=Config.NO_USER_CODE_ID, code="NU", short_description="No User", long_description="No user is logged onto the machine", graph_colour="#ffffff") db.session.add(no_user_code) unexplained_code = ActivityCode(id=Config.UNEXPLAINED_DOWNTIME_CODE_ID, code="DO", short_description='Down', long_description="Downtime that doesn't have an explanation from the user", graph_colour='#b22222') db.session.add(unexplained_code) uptime_code = ActivityCode(id=Config.UPTIME_CODE_ID, code="UP", short_description='Up', long_description='The machine is in use', graph_colour='#00ff80') db.session.add(uptime_code) setting_code = ActivityCode(id=Config.SETTING_CODE_ID, code="ST", short_description="Setting", long_description="The machine is being set up", graph_colour="#ff8000") db.session.add(setting_code) db.session.commit() current_app.logger.info("Created default activity codes on first startup") if len(Schedule.query.all()) == 0: schedule1 = Schedule(name="Default", mon_start=time(hour=6).strftime(SHIFT_STRFTIME_FORMAT), mon_end=time(hour=22).strftime(SHIFT_STRFTIME_FORMAT), tue_start=time(hour=6).strftime(SHIFT_STRFTIME_FORMAT), tue_end=time(hour=22).strftime(SHIFT_STRFTIME_FORMAT), wed_start=time(hour=6).strftime(SHIFT_STRFTIME_FORMAT), wed_end=time(hour=22).strftime(SHIFT_STRFTIME_FORMAT), thu_start=time(hour=6).strftime(SHIFT_STRFTIME_FORMAT), thu_end=time(hour=22).strftime(SHIFT_STRFTIME_FORMAT), fri_start=time(hour=6).strftime(SHIFT_STRFTIME_FORMAT), fri_end=time(hour=22).strftime(SHIFT_STRFTIME_FORMAT), sat_start=time(hour=6).strftime(SHIFT_STRFTIME_FORMAT), sat_end=time(hour=22).strftime(SHIFT_STRFTIME_FORMAT), sun_start=time(hour=6).strftime(SHIFT_STRFTIME_FORMAT), sun_end=time(hour=22).strftime(SHIFT_STRFTIME_FORMAT)) db.session.add(schedule1) db.session.commit() current_app.logger.info("Created default schedule on first startup") if len(WorkflowType.query.all()) == 0: default = WorkflowType(name="Default", description="start job > " "screen to select machine status with live updates > " "enter parts >" "end job") db.session.add(default) db.session.commit() current_app.logger.info("Created default workflow type on first startup") pneumatrol1 = WorkflowType(name="Pneumatrol_setting", description="start job or setting decided by server > " "active screen with option to pause > " "pause screen gives option to select reason for pause > " "resume back to active screen >" "enter parts >" "end job") db.session.add(pneumatrol1) db.session.commit() current_app.logger.info("Created pneumatrol_setting workflow type on first startup") pneumatrol2 = WorkflowType(name="Pneumatrol_no_setting", description="start job screen> " "active screen with option to pause > " "pause screen gives option to select reason for pause > " "resume back to active screen >" "enter parts >" "end job") db.session.add(pneumatrol2) db.session.commit() current_app.logger.info("Created pneumatrol_no_setting workflow type on first startup") if len(MachineGroup.query.all()) == 0: group1 = MachineGroup(name="Group 1") current_app.logger.info("Created default machine group on first startup") db.session.add(group1) db.session.commit() if len(Machine.query.all()) == 0: machine1 = Machine(name="Machine 1", device_ip="127.0.0.1", group_id=1, schedule_id=1) db.session.add(machine1) db.session.commit() current_app.logger.info("Created default machine on first startup") act = Activity(machine_id=machine1.id, timestamp_start=datetime.now().timestamp(), machine_state=Config.MACHINE_STATE_OFF, activity_code_id=Config.NO_USER_CODE_ID) db.session.add(act) db.session.commit() current_app.logger.info("Created activity on first startup") db.session.commit() if len(Settings.query.all()) == 0: settings = Settings(threshold=500, dashboard_update_interval_s=10) db.session.add(settings) db.session.commit() current_app.logger.info("Created default settings on first startup")