├── .gitignore ├── requirements.txt ├── .DS_Store ├── Module 5 ├── mes_project_20221119085516.zip ├── Vision Windows and Templates Current.zip ├── mes_core_updates.sql ├── mes_custom.sql └── utiliites.util.py ├── Module 2 ├── mes_core.py ├── mysql_test.py └── mes_core.count.py ├── Module 6 ├── alter_tables.sql ├── count_tag_event_script.py └── createWorkOrder.py ├── Module 1 ├── UDT & Namespace Example.txt ├── 1_create_enterprise_tables.sql ├── 2_create_oee_downtime_tables.sql └── 3_create_product_and_schedule_tables.sql ├── software_requirements.txt ├── README.md ├── LICENSE ├── main.py ├── Module 3 ├── mes_core_package.txt ├── mes_core.logging.py ├── mes_core.state.py ├── mes_core.order.py ├── mes_core.run.py ├── mes_core.model.py └── mes_core.oee.py └── Module 4 ├── tags.json └── mes_core-udts.json /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mysql-connector-python==9.1.0 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackscriven/mba/HEAD/.DS_Store -------------------------------------------------------------------------------- /Module 5/mes_project_20221119085516.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackscriven/mba/HEAD/Module 5/mes_project_20221119085516.zip -------------------------------------------------------------------------------- /Module 5/Vision Windows and Templates Current.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackscriven/mba/HEAD/Module 5/Vision Windows and Templates Current.zip -------------------------------------------------------------------------------- /Module 2/mes_core.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package is for an external core_mes system 3 | """ 4 | 5 | def moduleTest(printStatement): 6 | print('Start Test') 7 | print(printStatement) 8 | return 1 9 | -------------------------------------------------------------------------------- /Module 6/alter_tables.sql: -------------------------------------------------------------------------------- 1 | 2 | -- allow the product code ID to be null 3 | -- run this code against your mes_core database 4 | ALTER TABLE `mes_core`.`workorder` 5 | CHANGE COLUMN `ProductCodeID` `ProductCodeID` INT NULL ; 6 | 7 | 8 | 9 | -- alter the schedule table ScheduleType datatype to VARCHAR(45) 10 | ALTER TABLE `mes_core`.`schedule` 11 | CHANGE COLUMN `ScheduleType` `ScheduleType` VARCHAR(45) NOT NULL ; 12 | -------------------------------------------------------------------------------- /Module 2/mysql_test.py: -------------------------------------------------------------------------------- 1 | print('Start Test') 2 | 3 | import mysql.connector 4 | 5 | # Establish the connection 6 | cnx = mysql.connector.connect( 7 | user='python', 8 | password='Bootcamp!', 9 | host='127.0.0.1', 10 | database='mes_core' 11 | ) 12 | 13 | try: 14 | # Create a cursor 15 | cursor = cnx.cursor() 16 | 17 | # Execute a simple SELECT query 18 | cursor.execute("SELECT * FROM enterprise") 19 | 20 | # Fetch all the rows from the query result 21 | result = cursor.fetchall() 22 | 23 | # Print the result 24 | print(result) 25 | 26 | finally: 27 | # Close the connection 28 | cnx.close() 29 | -------------------------------------------------------------------------------- /Module 5/mes_core_updates.sql: -------------------------------------------------------------------------------- 1 | 2 | -- adding default value to TimeStamp column 3 | ALTER TABLE `mes_core`.`enterprise` 4 | CHANGE COLUMN `TimeStamp` `TimeStamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ; 5 | 6 | ALTER TABLE `mes_core`.`site` 7 | CHANGE COLUMN `TimeStamp` `TimeStamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ; 8 | 9 | ALTER TABLE `mes_core`.`area` 10 | CHANGE COLUMN `TimeStamp` `TimeStamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ; 11 | 12 | ALTER TABLE `mes_core`.`line` 13 | CHANGE COLUMN `TimeStamp` `TimeStamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ; 14 | 15 | ALTER TABLE `mes_core`.`cell` 16 | CHANGE COLUMN `TimeStamp` `TimeStamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ; 17 | -------------------------------------------------------------------------------- /Module 1/UDT & Namespace Example.txt: -------------------------------------------------------------------------------- 1 | UDT and Namespace Development 2 | 3 | • UDT Dev 4 | • mes_core 5 | ○ Line 6 | ○ Count Dispatch 7 | ○ OEE-Downtime 8 | ○ Query Dispatch 9 | ○ UI 10 | • Namespace Dev 11 | • Enterprise 12 | ○ Site 13 | § Area 14 | ® Line 15 | ◊ Line UDT 16 | ◊ Edge UDT 17 | ◊ UI UDT 18 | • **Example Namespace** 19 | • Enterprise 20 | ○ Site 21 | § Area 22 | ® Line 23 | ◊ Line 24 | } Dispatch (Query Dispatch) 25 | – OEE Infeed (Count Dispatch) 26 | – OEE Outfeed (Count Dispatch) 27 | – OEE Waste (Count Dispatch) 28 | } OEE (OEE-Downtime) 29 | ◊ Edge (Physical Asset UDT) 30 | ◊ UI (UI UDT) -------------------------------------------------------------------------------- /software_requirements.txt: -------------------------------------------------------------------------------- 1 | # ===================================================================== 2 | # MES Bootcamp Accelerator - Required Software 3 | # ===================================================================== 4 | 5 | # IIoT Platform: Ignition 8.1.44 6 | # https://inductiveautomation.com/downloads/ignition/8.1.44 7 | 8 | # Database: MySQL 8.0.40 9 | # https://dev.mysql.com/downloads/installer/ 10 | 11 | # Database Admin Tool: MySQL Workbench 8.0.40 12 | # https://dev.mysql.com/downloads/workbench/ 13 | 14 | # Code Editor: Visual Studio Code 15 | # https://code.visualstudio.com/Download 16 | 17 | # Python IDE: PyCharm Community Edition 2024.3.1.1 18 | # https://www.jetbrains.com/pycharm/download/ 19 | 20 | # python -- version 3.13.0 21 | # pip -- version 22.0.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mba 2 | MES Bootcamp Accelerator 3 | 4 | Welcome to the MES Bootcamp Accelerator, a simplified resource for students of the MES Bootcamp from IIoT University, taught by Walker Reynolds and coached by Zack Scriven. This repo contains the bare essentials for building a basic MES (Manufacturing Execution System) using a SQL backend and a Python API. 5 | 6 | Getting Started 7 | 8 | Clone the repository. https://github.com/zackscriven/mba 9 | Install necessary dependencies (see requirements.txt). 10 | Configure your SQL database credentials in the provided Python scripts. 11 | Run the sample script(s) to confirm your database connection. 12 | Feel free to open an issue or pull request if you have questions or suggestions. Good luck, and enjoy building your MES core system! 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 4.0 Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Module 6/count_tag_event_script.py: -------------------------------------------------------------------------------- 1 | def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents): 2 | """ 3 | Tag Event Script for valueChanged on the Count tag. 4 | This script will call the storeCountHistory function from the mes_core.count module. 5 | """ 6 | from mes_core.count import storeCountHistory 7 | 8 | if initialChange: 9 | return # Ignore the initial tag change event 10 | 11 | # Correct tag paths using relative reference in the same folder 12 | tagPaths = [ 13 | "[.]Enable", 14 | "[.]LastCount", 15 | "[.]TagID", 16 | "[.]CountTypeID" 17 | ] 18 | 19 | # Read tag values in one call 20 | tagValues = system.tag.readBlocking(tagPaths) 21 | 22 | enable = tagValues[0].value 23 | lastCount = tagValues[1].value 24 | tagID = tagValues[2].value 25 | countTypeID = tagValues[3].value 26 | 27 | # Check if the counter is enabled 28 | if enable == 1: 29 | # Call storeCountHistory and check the result 30 | result = storeCountHistory(currentValue.value, lastCount, tagID, countTypeID) 31 | 32 | # Update LastCount tag if the count delta was successfully recorded 33 | if result != -1: 34 | system.tag.writeBlocking(["[.]LastCount"], [result]) 35 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # This is a sample Python script. 2 | 3 | # Press Shift+F10 to execute it or replace it with your code. 4 | # Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. 5 | 6 | print('Zack') 7 | print('') 8 | 9 | def print_hi(name): 10 | # Use a breakpoint in the code line below to debug your script. 11 | print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. 12 | 13 | # Press the green button in the gutter to run the script. 14 | if __name__ == '__main__': 15 | print_hi('mes_core') 16 | 17 | # See PyCharm help at https://www.jetbrains.com/help/pycharm/ 18 | # Start of Test Query Code -- retrieve record from enterprise table in mes_core schema 19 | # Connect to MySQL db 20 | 21 | print("Start Test") 22 | 23 | import mysql.connector 24 | 25 | # Establishing the connection 26 | cnx = mysql.connector.connect( 27 | user='Root', 28 | password='MySQLadmin42', 29 | host='127.0.0.1', 30 | database='mes_core' 31 | ) 32 | 33 | try: 34 | # Creating a cursor to execute queries 35 | cursor = cnx.cursor() 36 | cursor.execute("SELECT * FROM enterprise") 37 | 38 | # Fetching all results from the query 39 | result = cursor.fetchall() 40 | print(result) 41 | 42 | finally: 43 | # Closing the connection 44 | cnx.close() 45 | -------------------------------------------------------------------------------- /Module 5/mes_custom.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `nav_tree`; 2 | 3 | SET @saved_cs_client = @@character_set_client; 4 | SET character_set_client = utf8mb4; 5 | 6 | CREATE TABLE `nav_tree` ( 7 | `id` INT(11) NOT NULL AUTO_INCREMENT, 8 | `path` VARCHAR(1536) DEFAULT NULL, 9 | `text` VARCHAR(765) DEFAULT NULL, 10 | `type` VARCHAR(135) DEFAULT NULL, 11 | `security` VARCHAR(765) DEFAULT NULL, 12 | `command` VARCHAR(765) DEFAULT NULL, 13 | `icon` VARCHAR(765) DEFAULT NULL, 14 | `order` INT(10) DEFAULT NULL, 15 | `mode` VARCHAR(135) DEFAULT '', 16 | PRIMARY KEY (`id`) 17 | ) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8; 18 | 19 | SET character_set_client = @saved_cs_client; 20 | 21 | 22 | DROP TABLE IF EXISTS `nav_tree_vars`; 23 | 24 | SET @saved_cs_client = @@character_set_client; 25 | SET character_set_client = utf8mb4; 26 | 27 | CREATE TABLE `nav_tree_vars` ( 28 | `nav_tree_new_id` INT(11) NOT NULL AUTO_INCREMENT, 29 | `property` VARCHAR(765) DEFAULT NULL, 30 | `value` VARCHAR(765) DEFAULT NULL, 31 | PRIMARY KEY (`nav_tree_new_id`) 32 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 33 | 34 | SET character_set_client = @saved_cs_client; 35 | SET TIME_ZONE = @@TIME_ZONE; 36 | 37 | /* Additional settings */ 38 | SET SQL_MODE = @OLD_SQL_MODE; 39 | SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS; 40 | SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS; 41 | SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT; 42 | SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS; 43 | SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION; 44 | SET SQL_NOTES = @OLD_SQL_NOTES; 45 | -------------------------------------------------------------------------------- /Module 2/mes_core.count.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This script is for handling count events from ignition tags. Events are summed and stored in the mes_core database. 3 | ''' 4 | 5 | def storeCountHistory(currentCount, lastCount, tagID, countTypeID, db='mes_core'): 6 | ''' 7 | This function takes four arguments from the tag event--the currentCount, lastCount, tagID, and countTypeID. 8 | When called from the tag event, this function will calculate the count delta and then insert a new count 9 | history record with the delta, count type, tagID, and timestamp. We return the currentValue to the tag event 10 | so the value can be written to the last count tag to be used by the next count delta calculation. 11 | 12 | Parameters: 13 | currentCount (int): The current count value. 14 | lastCount (int): The last count value. 15 | tagID (int): The ID of the tag associated with the count. 16 | countTypeID (int): The ID representing the type of count. 17 | db (str): The database name to use for the query. Defaults to 'mes_core'. 18 | 19 | Returns: 20 | int: The currentCount if the delta is >= 1; otherwise, -1. 21 | ''' 22 | import system 23 | from datetime import datetime 24 | import time 25 | 26 | stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 27 | countDelta = currentCount - lastCount 28 | 29 | if abs(countDelta) >= 1: 30 | query = 'INSERT INTO counthistory (TagID, CountTypeID, Count, TimeStamp) VALUES (?, ?, ?, ?)' 31 | system.db.runSFPrepUpdate(query, [tagID, countTypeID, countDelta, stamp], [db]) 32 | return currentCount 33 | else: 34 | return -1 35 | -------------------------------------------------------------------------------- /Module 6/createWorkOrder.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from datetime import datetime 4 | # Retrieve parent container for easier reference 5 | parent = event.source.parent 6 | 7 | # Get product code from the selected row in the table 8 | productCode = event.source.parent.selectedProductCode 9 | productCodeID = event.source.parent.selectedProductCodeID 10 | 11 | # Replace spaces with underscores in the product code 12 | productCodeFormatted = productCode.replace(" ", "_") 13 | 14 | # Get quantity from the QuantityField component 15 | quantity = parent.getComponent('QuantityField').intValue 16 | 17 | # Database connection name 18 | db = 'mes_core' 19 | 20 | # Format current timestamp as YYYYMMDD for the work order 21 | timestamp = datetime.now().strftime("%Y%m%d") 22 | 23 | # Generate a sequence number (001 as a placeholder for now) 24 | sequence = "001" # Start at 001 if no previous sequence 25 | #query = "SELECT MAX(SUBSTRING_INDEX(WorkOrder, '-', -1)) FROM workorder WHERE ProductCode = ?" 26 | #result = system.db.runPrepQuery(query, [productCode], 'mes_core') 27 | # 28 | #if result[0][0] is None: 29 | # sequence = "001" # Start at 001 if no previous sequence 30 | #else: 31 | # sequence = str(int(result[0][0]) + 1).zfill(3) # Increment and pad with zeros 32 | 33 | # Construct the work order name using product code, timestamp, and sequence 34 | workOrder = "WO-{productCodeFormatted}-{timestamp}-{sequence}".format(productCodeFormatted=productCodeFormatted, timestamp=timestamp, sequence=sequence) 35 | 36 | # SQL query to insert a new work order into the database 37 | query = """ 38 | INSERT INTO WorkOrder (WorkOrder, Quantity, Closed, Hide, TimeStamp, ProductCode, ProductCodeID) 39 | VALUES (?, ?, ?, ?, ?, ?, ?) 40 | """ 41 | 42 | # Prepare the arguments for the query 43 | args = [workOrder, quantity, 0, 0, datetime.now(), productCode, productCodeID] # Replace 1 with the actual ProductCodeID if available 44 | 45 | # Execute the SQL query using Ignition's system.db.runSFPrepUpdate 46 | system.db.runSFPrepUpdate(query, args, [db]) 47 | 48 | # Log the work order creation for debugging (optional) 49 | system.util.getLogger("WorkOrderScript").info("Work Order created: {}".format(workOrder)) -------------------------------------------------------------------------------- /Module 3/mes_core_package.txt: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | mes_core Package 24 | 25 | • mes_core (pkg) 26 | ○ count (mod) 27 | § storeCountHistory (fx) 28 | ○ logging (mod) 29 | § log (fx) 30 | ○ model (mod) 31 | § MESFilterResults (class) 32 | § MESFilter (class) 33 | § createFilter (fx) 34 | § loadMESObjects (fx) 35 | § loadMESObject (fx) 36 | § getEnterpriseID (fx) 37 | § getSiteID (fx) 38 | § getAreaID (fx) 39 | § getLineID (fx) 40 | § getCellID (fx) 41 | § getID (fx) 42 | ○ oee (mod) 43 | § calcQuality (fx) 44 | § calcAvailability (fx) 45 | § calcPerformance (fx) 46 | § getTagIDs (fx) 47 | § getGoodCount (fx) 48 | § getBadCount (fx) 49 | § getTotalCount (fx) 50 | § getUnplannedDowntimeSeconds (fx) 51 | § getPlannedDowntimeSeconds (fx) 52 | § getOee (fx) 53 | ○ order (mod) 54 | § addProductCode (fx) 55 | § updateProductCodeLineStatus (fx) 56 | § addWorkOrderEntry (fx) 57 | § updateWorkOrderEntry (fx) 58 | ○ run (mod) 59 | § calcFinishTime (fx) 60 | § updateRun (fx) 61 | ○ state (mod) 62 | storeStateHistory (fx) -------------------------------------------------------------------------------- /Module 1/1_create_enterprise_tables.sql: -------------------------------------------------------------------------------- 1 | -- This table stores enterprise-level data for the MES system, 2 | -- including a primary ID, name, timestamp, 3 | -- and a flag for active/disabled status. 4 | CREATE TABLE `mes_core`.`enterprise` ( 5 | `ID` INT NOT NULL AUTO_INCREMENT, 6 | `Name` VARCHAR(45) NOT NULL, 7 | `TimeStamp` DATETIME NOT NULL, 8 | `Disable` TINYINT NOT NULL DEFAULT 0, 9 | PRIMARY KEY (`ID`), 10 | UNIQUE INDEX `Name_UNIQUE` (`Name` ASC) VISIBLE 11 | ); 12 | 13 | -- This table stores site-level data in the MES system. 14 | -- The `ParentID` column references the enterprise table 15 | -- (foreign key to be added later as homework). 16 | CREATE TABLE `mes_core`.`site` ( 17 | `ID` INT NOT NULL AUTO_INCREMENT, 18 | `Name` VARCHAR(45) NOT NULL, 19 | `TimeStamp` DATETIME NOT NULL, 20 | `Disable` TINYINT NULL DEFAULT 0, 21 | `ParentID` INT NOT NULL, 22 | PRIMARY KEY (`ID`), 23 | UNIQUE INDEX `Name_UNIQUE` (`Name` ASC) VISIBLE 24 | ); 25 | 26 | 27 | -- This table stores area-level data in the MES system. 28 | -- The `ParentID` column references the site table 29 | -- (foreign key to be added later as homework). 30 | CREATE TABLE `mes_core`.`area` ( 31 | `ID` INT NOT NULL AUTO_INCREMENT, 32 | `Name` VARCHAR(45) NOT NULL, 33 | `TimeStamp` DATETIME NOT NULL, 34 | `Disable` TINYINT NULL DEFAULT 0, 35 | `ParentID` INT NOT NULL, 36 | PRIMARY KEY (`ID`), 37 | UNIQUE INDEX `Name_UNIQUE` (`Name` ASC) VISIBLE 38 | ); 39 | 40 | -- This table stores line-level data in the MES system. 41 | -- The `ParentID` column references the area table 42 | -- (foreign key to be added later as homework). 43 | CREATE TABLE `mes_core`.`line` ( 44 | `ID` INT NOT NULL AUTO_INCREMENT, 45 | `Name` VARCHAR(45) NOT NULL, 46 | `TimeStamp` DATETIME NOT NULL, 47 | `Disable` TINYINT NULL DEFAULT 0, 48 | `ParentID` INT NOT NULL, 49 | PRIMARY KEY (`ID`) 50 | ); 51 | 52 | 53 | -- This table stores cell-level data in the MES system. 54 | -- The `ParentID` column references the line table 55 | -- (foreign key to be added later as homework). 56 | CREATE TABLE `mes_core`.`cell` ( 57 | `ID` INT NOT NULL AUTO_INCREMENT, 58 | `Name` VARCHAR(45) NOT NULL, 59 | `TimeStamp` DATETIME NOT NULL, 60 | `Disable` TINYINT DEFAULT '0', 61 | `ParentID` INT NOT NULL, 62 | PRIMARY KEY (`ID`) 63 | ); 64 | -------------------------------------------------------------------------------- /Module 5/utiliites.util.py: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | import re 24 | htmlPatternBR = re.compile(r'
', re.I) 25 | htmlPatternGeneric = re.compile('<.*?>', re.I) 26 | 27 | def stripHTML(string, handleBreaks=False): 28 | """This removes the HTML markup tags from a string. 29 | Not to be confused with parsing, of which this does *NONE*. 30 | https://stackoverflow.com/a/1732454 31 | """ 32 | if handleBreaks: 33 | # Line breaks are line feeds 34 | string = htmlPatternBR.sub('\n', string) 35 | 36 | # Everything else can be stripped 37 | string = htmlPatternGeneric.sub('', string) 38 | 39 | return string 40 | 41 | def clipboard(imagePath): 42 | from java.awt.datatransfer import StringSelection 43 | from java.awt.datatransfer import Clipboard 44 | from java.awt import Toolkit 45 | from java.awt.datatransfer import DataFlavor 46 | 47 | toolkit = Toolkit.getDefaultToolkit() 48 | clipboard = toolkit.getSystemClipboard() 49 | clipboard.setContents(StringSelection(imagePath), None) 50 | contents = clipboard.getContents(None) 51 | 52 | logger = system.util.getLogger("IntellicInfo") 53 | content = contents.getTransferData(DataFlavor.stringFlavor) 54 | logger.infof('Image %s copied to clipboard. Content: %s', imagePath, content) 55 | 56 | def retarget(project): 57 | system.util.retarget(project) -------------------------------------------------------------------------------- /Module 3/mes_core.logging.py: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | ''' 24 | This script is designed to take two arguments, [message, level], 25 | for the Ignition Logging engine. The `message` parameter is the 26 | payload published to the MES logger, and `level` represents the 27 | severity of the log. Supported log levels are: 28 | 29 | - fatal 30 | - error 31 | - warn 32 | - info 33 | - debug 34 | - trace 35 | ''' 36 | 37 | def log(message, level): 38 | import system 39 | 40 | # Initialize the logger for the MES system 41 | logger = system.util.getLogger('MES') 42 | 43 | # Map the level argument to the corresponding log level 44 | if level.lower() == 'info': 45 | logger.info(message) 46 | elif level.lower() == 'fatal': 47 | logger.fatal(message) 48 | elif level.lower() == 'error': 49 | logger.error(message) 50 | elif level.lower() == 'warn': 51 | logger.warn(message) 52 | elif level.lower() == 'debug': 53 | logger.debug(message) 54 | elif level.lower() == 'trace': 55 | logger.trace(message) 56 | else: 57 | # Handle unsupported log levels 58 | # logger.warn(f"Unsupported log level: {level}. Defaulting to 'info'.") # deleted f-string 59 | 60 | # using .format() instead of f-string 61 | logger.warn("Unsupported log level: {}. Defaulting to 'info'.".format(level)) 62 | 63 | logger.info(message) 64 | -------------------------------------------------------------------------------- /Module 3/mes_core.state.py: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | """ 24 | Description: 25 | This module provides functionality for managing state history within the MES Core database. 26 | It includes functions to handle updates and insertions into the `statehistory` table based 27 | on changes in state reasons and associated line identifiers. 28 | 29 | Functions: 30 | - storeStateHistory: Updates the end time for the previous state history entry and 31 | inserts a new entry for the current state based on the provided reasonCode and lineID. 32 | 33 | """ 34 | 35 | #default database name 36 | db = 'mes_core' 37 | 38 | def storeStateHistory(reasonCode, lineID, db=db): 39 | ''' 40 | This function takes two arguments from the tag event -- the reasonCode and the lineID. 41 | When called from the tag event, this function will fill in the end time of the previous 42 | state history entry and create a new entry for the current state. 43 | 44 | Parameters: 45 | reasonCode (int): The code for the reason of the state change. 46 | lineID (int): The ID of the line associated with the state change. 47 | db (str): The database name to use for the query. Defaults to 'mes_core'. 48 | 49 | Returns: 50 | None 51 | ''' 52 | import system 53 | from datetime import datetime 54 | import time 55 | 56 | try: 57 | 58 | # Current timestamp 59 | stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 60 | 61 | # Retrieve reasonID and reasonName based on reasonCode and lineID 62 | data = system.db.runPrepQuery( 63 | 'SELECT ID, ReasonName FROM statereason WHERE ReasonCode = ? AND ParentID = ?', 64 | [reasonCode, lineID], 65 | db 66 | ) 67 | 68 | if len(data) > 0: 69 | reasonID, reasonName = data[0][0], data[0][1] 70 | else: 71 | system.util.getLogger("storeStateHistory").warn("No matching reason found for reasonCode: {}, lineID: {}".format(reasonCode, lineID)) 72 | return 73 | 74 | # Update the end time of the previous state history entry 75 | endQuery = 'UPDATE statehistory SET EndDateTime = ? WHERE LineID = ? AND EndDateTime IS NULL' 76 | system.db.runSFPrepUpdate(endQuery, [stamp, lineID], [db]) 77 | 78 | # Wait briefly before inserting the new state (simulate realistic processing delay) 79 | time.sleep(2) 80 | 81 | # Insert a new state history entry 82 | query = ('INSERT INTO statehistory (StateReasonID, ReasonName, LineID, ReasonCode, StartDateTime) ' 83 | 'VALUES (?, ?, ?, ?, ?)') 84 | system.db.runSFPrepUpdate(query, [reasonID, reasonName, lineID, reasonCode, stamp], [db]) 85 | 86 | # info logging for debugging 87 | system.util.getLogger("storeStateHistory").info("New state stored successfully for reasonCode: {}, lineID: {}".format(reasonCode, lineID)) 88 | 89 | except Exception as e: 90 | system.util.getLogger("storeStateHistory").error("Error storing state history: {}".format(str(e))) 91 | -------------------------------------------------------------------------------- /Module 1/2_create_oee_downtime_tables.sql: -------------------------------------------------------------------------------- 1 | -- ===================================================================== 2 | -- MES Core Tables for Bootcamp Accelerator 3 | -- ===================================================================== 4 | -- This script creates all the necessary tables for the MES Bootcamp 5 | -- Accelerator. These tables support the core MES functionality, 6 | -- including count tracking, state reasons, and state history. 7 | -- Foreign keys are omitted for now and will be added later. 8 | 9 | -- ===================================================================== 10 | -- Table: counttype 11 | -- Defines different types of counts for the MES Engine. 12 | -- ===================================================================== 13 | CREATE TABLE `mes_core`.`counttype` ( 14 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 15 | `CountType` VARCHAR(45) NOT NULL, -- Name of the count type (e.g., Good, Reject) 16 | PRIMARY KEY (`ID`), -- Ensures unique identification for each record 17 | UNIQUE INDEX `CountType_UNIQUE` (`CountType` ASC) VISIBLE -- Prevents duplicate count types 18 | ); 19 | 20 | -- ===================================================================== 21 | -- Table: counttag 22 | -- Stores tag paths associated with counts in the MES Engine. 23 | -- ===================================================================== 24 | CREATE TABLE `mes_core`.`counttag` ( 25 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 26 | `TagPath` VARCHAR(100) NOT NULL, -- Tag path for the count 27 | `ParentID` INT NOT NULL, -- References the count type (foreign key to be added later) 28 | PRIMARY KEY (`ID`), -- Ensures unique identification for each record 29 | UNIQUE INDEX `TagPath_UNIQUE` (`TagPath` ASC) VISIBLE -- Prevents duplicate tag paths 30 | ); 31 | 32 | -- ===================================================================== 33 | -- Table: counthistory 34 | -- Records the count history, including metadata such as timestamps. 35 | -- ===================================================================== 36 | CREATE TABLE `mes_core`.`counthistory` ( 37 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 38 | `TagID` INT NOT NULL, -- References the tag associated with this count (foreign key to be added later) 39 | `CountTypeID` INT NOT NULL, -- References the count type (foreign key to be added later) 40 | `Count` INT NOT NULL, -- Numeric count value 41 | `TimeStamp` DATETIME NOT NULL, -- Timestamp when the count occurred 42 | `RunID` INT NULL, -- References the production run (foreign key to be added later) 43 | PRIMARY KEY (`ID`) -- Ensures unique identification for each record 44 | ); 45 | 46 | -- ===================================================================== 47 | -- Table: statereason 48 | -- Defines state reasons for the MES Engine, including downtime reasons. 49 | -- ===================================================================== 50 | CREATE TABLE `mes_core`.`statereason` ( 51 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 52 | `ParentID` INT NOT NULL, -- References a parent state or category 53 | `ReasonName` VARCHAR(45) NOT NULL, -- Name of the state reason 54 | `ReasonCode` INT NOT NULL, -- Unique code for the reason 55 | `RecordDowntime` TINYINT NOT NULL DEFAULT 0, -- Indicates whether downtime is recorded 56 | `PlannedDowntime` TINYINT NOT NULL DEFAULT 0, -- Indicates planned downtime 57 | `OperatorSelectable` TINYINT NOT NULL DEFAULT 0, -- Indicates if the reason is operator-selectable 58 | `SubReasonOf` INT NULL, -- References a parent reason if this is a sub-reason 59 | PRIMARY KEY (`ID`) -- Ensures unique identification for each record 60 | ); 61 | 62 | -- ===================================================================== 63 | -- Table: statehistory 64 | -- Records state history events, referencing state reasons. 65 | -- ===================================================================== 66 | CREATE TABLE `mes_core`.`statehistory` ( 67 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key, unique identifier for each record 68 | `StateReasonID` INT NOT NULL, -- References the state reason associated with this history (foreign key can be added later) 69 | `StartDateTime` DATETIME NOT NULL, -- Start timestamp of the state 70 | `EndDateTime` DATETIME NOT NULL, -- End timestamp of the state 71 | `Note` VARCHAR(100) NULL, -- Optional notes or comments about the state 72 | `LineID` INT NOT NULL, -- References the line where the state occurred (foreign key can be added later) 73 | `RunID` INT NOT NULL, -- References the production run (foreign key can be added later) 74 | PRIMARY KEY (`ID`) -- Ensures unique identification for each record 75 | ); 76 | -------------------------------------------------------------------------------- /Module 3/mes_core.order.py: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | ''' 24 | This script handles operations related to product codes 25 | in the MES core database. It includes functionality for 26 | adding product codes and ensuring they are updated if 27 | duplicates exist. 28 | ''' 29 | #default database name 30 | db = 'mes_core' 31 | 32 | from mes_core.model import getLineID 33 | from mes_core.logging import log 34 | 35 | # Function to add or update a product code 36 | def addProductCode(productCode, description, disable=0, db=db): 37 | ''' 38 | This function inserts a new product code into the database or updates 39 | an existing entry if the product code already exists. 40 | 41 | Parameters: 42 | - productCode: The unique code for the product. 43 | - description: A text description of the product. 44 | - disable: A flag to indicate if the product is disabled (default is 0). 45 | - db: The name of the database (default is 'mes_core'). 46 | 47 | Returns: 48 | - The primary key of the inserted or updated product code. 49 | ''' 50 | try: 51 | query = """ 52 | INSERT INTO productcode (ProductCode, Description, Disable, TimeStamp) 53 | VALUES (?, ?, ?, NOW()) 54 | ON DUPLICATE KEY UPDATE 55 | ProductCode = VALUES(ProductCode), 56 | Description = VALUES(Description), 57 | Disable = VALUES(Disable), 58 | TimeStamp = NOW() 59 | """ 60 | key = system.db.runPrepUpdate(query, [productCode, description, disable], db, getKey=1) 61 | return key 62 | except Exception as e: 63 | log("Error in addProductCode: {}".format(str(e)), 'error') 64 | return None 65 | 66 | # Function to update the product code line status 67 | def updateProductCodeLineStatus(productCode, modelPath, enable=True, db=db): 68 | ''' 69 | This function updates or inserts a record in the product code line table based on the 70 | provided product code and model path. 71 | 72 | Parameters: 73 | - productCode: The product code to update or insert. 74 | - modelPath: The path used to determine the line ID. 75 | - enable: A boolean indicating whether to enable or disable the product code line (default: True). 76 | - db: The database name (default: 'MES_Core'). 77 | 78 | Returns: 79 | - 1 if the operation succeeds. 80 | - 0 if an error occurs. 81 | ''' 82 | try: 83 | # Retrieve product code ID 84 | productCodeID = system.db.runScalarPrepQuery(""" 85 | SELECT ID FROM productcode 86 | WHERE ProductCode = ? 87 | """, [productCode], db) 88 | 89 | # Retrieve line ID 90 | lineID = getLineID(modelPath) 91 | 92 | # Check if the product code line already exists 93 | pcID = system.db.runScalarPrepQuery(""" 94 | SELECT ID FROM productcodeline 95 | WHERE ProductCodeID = ? AND LineID = ? 96 | """, [productCodeID, lineID], db) 97 | 98 | # Update or insert the product code line 99 | if pcID: 100 | system.db.runPrepUpdate(""" 101 | UPDATE productcodeline 102 | SET Enable = ?, TimeStamp = NOW() 103 | WHERE ProductCodeID = ? AND LineID = ? 104 | """, [enable, productCodeID, lineID], db) 105 | else: 106 | system.db.runPrepUpdate(""" 107 | INSERT INTO productcodeline (ProductCodeID, LineID, Enable, TimeStamp) 108 | VALUES (?, ?, ?, NOW()) 109 | """, [productCodeID, lineID, enable], db) 110 | 111 | return 1 112 | except Exception as e: 113 | log("Error in updateProductCodeLineStatus: {}".format(str(e)), 'error') 114 | return 0 115 | 116 | 117 | # Function to add a work order entry 118 | def addWorkOrderEntry(workOrder, productCode, quantity, db=db): 119 | ''' 120 | This function adds a work order entry to the database. If the work order already exists, 121 | it updates the quantity, product code ID, product code, and timestamp. 122 | 123 | Parameters: 124 | - workOrder: The work order identifier. 125 | - productCode: The associated product code. 126 | - quantity: The quantity for the work order. 127 | - db: The database name (default: 'mes_Core'). 128 | 129 | Returns: 130 | - None. 131 | ''' 132 | try: 133 | # Retrieve the product code ID 134 | pcID = system.db.runScalarPrepQuery(""" 135 | SELECT ID 136 | FROM productcode 137 | WHERE ProductCode = ? 138 | """, [productCode], db) 139 | 140 | # Insert or update the work order entry 141 | query = """ 142 | INSERT INTO workorder (WorkOrder, Quantity, Closed, Hide, TimeStamp, ProductCodeID, ProductCode) 143 | VALUES (?, ?, 0, 0, NOW(), ?, ?) 144 | ON DUPLICATE KEY UPDATE 145 | Quantity = VALUES(Quantity), 146 | ProductCodeID = VALUES(ProductCodeID), 147 | ProductCode = VALUES(ProductCode), 148 | TimeStamp = NOW() 149 | """ 150 | system.db.runPrepUpdate(query, [workOrder, quantity, pcID, productCode], db) 151 | except Exception as e: 152 | log("Error in addWorkOrderEntry: {}".format(str(e)), 'error') 153 | return 0 -------------------------------------------------------------------------------- /Module 1/3_create_product_and_schedule_tables.sql: -------------------------------------------------------------------------------- 1 | -- ===================================================================== 2 | -- MES Work Order Tables 3 | -- ===================================================================== 4 | -- This script creates tables related to work orders and their management 5 | -- in the MES system. These tables are used to track work order details, 6 | -- production quantities, product codes, and timestamps. 7 | -- ===================================================================== 8 | 9 | -- ===================================================================== 10 | -- Table: workorder 11 | -- Stores work order information, including quantity, status, and product details. 12 | -- ===================================================================== 13 | CREATE TABLE `mes_core`.`workorder` ( 14 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 15 | `WorkOrder` VARCHAR(100) NOT NULL, -- Unique identifier for the work order 16 | `Quantity` INT NOT NULL, -- Total quantity associated with the work order 17 | `Closed` TINYINT NULL, -- Indicates if the work order is closed (1 = closed, 0 = open) 18 | `Hide` TINYINT NULL DEFAULT 0, -- Determines if the work order should be hidden in the UI 19 | `TimeStamp` DATETIME NOT NULL, -- Timestamp when the work order was created or updated 20 | `ProductCodeID` INT NOT NULL, -- References the product associated with this work order (foreign key can be added later) 21 | `ProductCode` VARCHAR(100) NOT NULL, -- Code representing the product for the work order 22 | PRIMARY KEY (`ID`), -- Ensures unique identification for each record 23 | UNIQUE INDEX `WorkOrder_UNIQUE` (`WorkOrder` ASC) VISIBLE -- Prevents duplicate work orders 24 | ); 25 | 26 | -- ===================================================================== 27 | -- Table: productcode 28 | -- Stores product code information, including descriptions and status. 29 | -- ===================================================================== 30 | CREATE TABLE `mes_core`.`productcode` ( 31 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 32 | `ProductCode` VARCHAR(100) NOT NULL, -- Unique identifier for the product 33 | `Description` VARCHAR(255) NULL, -- Description of the product 34 | `Disable` TINYINT NULL DEFAULT 0, -- Determines if the product code is disabled (1 = disabled, 0 = enabled) 35 | `TimeStamp` DATETIME NOT NULL, -- Timestamp when the product code was created or updated 36 | PRIMARY KEY (`ID`) -- Ensures unique identification for each record 37 | ); 38 | 39 | -- ===================================================================== 40 | -- Table: productcodeline 41 | -- Links product codes to specific production lines and tracks their 42 | -- enablement status and timestamps. 43 | -- ===================================================================== 44 | CREATE TABLE `mes_core`.`productcodeline` ( 45 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 46 | `ProductCodeID` INT NOT NULL, -- Foreign key to the productcode table (to be defined later) 47 | `LineID` INT NOT NULL, -- Foreign key to the line table (to be defined later) 48 | `Enable` TINYINT NOT NULL DEFAULT 1, -- Indicates if the product is enabled on the line (1 = enabled, 0 = disabled) 49 | `TimeStamp` DATETIME NOT NULL, -- Timestamp for when the record was created or updated 50 | PRIMARY KEY (`ID`) -- Ensures unique identification for each record 51 | ); 52 | 53 | -- ===================================================================== 54 | -- Table: schedule 55 | -- Stores scheduling information for production lines, including details 56 | -- about work orders, planned schedules, actual outcomes, and timestamps. 57 | -- ===================================================================== 58 | CREATE TABLE `mes_core`.`schedule` ( 59 | `ID` INT NOT NULL AUTO_INCREMENT, -- Primary Key 60 | `LineID` INT NOT NULL, -- Foreign key to the line table (to be defined later) 61 | `WorkOrderID` INT NOT NULL, -- Foreign key to the workorder table (to be defined later) 62 | `ScheduleType` INT NOT NULL, -- Type of schedule (e.g., planned, maintenance) 63 | `Note` VARCHAR(255) NULL, -- Optional notes related to the schedule 64 | `ScheduleStartDateTime` DATETIME NOT NULL, -- Planned start date and time for the schedule 65 | `ScheduleFinishDateTime` DATETIME NOT NULL, -- Planned finish date and time for the schedule 66 | `Quantity` INT NOT NULL, -- Quantity planned for the schedule 67 | `EnteredBy` VARCHAR(45) NULL, -- User who entered the schedule 68 | `TimeStamp` DATETIME NULL, -- Timestamp for when the record was created or updated 69 | `RunID` INT NULL, -- Identifier for the associated production run 70 | `ActualStartDateTime` DATETIME NULL, -- Actual start date and time for the schedule 71 | `ActualFinishDateTime` DATETIME NULL, -- Actual finish date and time for the schedule 72 | `ActualQuantity` INT NULL, -- Actual quantity achieved during the schedule 73 | `RunStartDateTime` DATETIME NULL, -- Start time of the associated production run 74 | PRIMARY KEY (`ID`) -- Ensures unique identification for each record 75 | ); 76 | 77 | -- This table stores detailed information about production runs 78 | -- and their associated metrics within the MES system. 79 | 80 | CREATE TABLE `mes_core`.`run` ( 81 | `ID` INT NOT NULL AUTO_INCREMENT, 82 | `ScheduleID` INT NOT NULL, 83 | `RunStartDateTime` DATETIME NULL, 84 | `RunStopDateTime` DATETIME NULL, 85 | `StartInfeed` INT NULL, 86 | `CurrentInfeed` INT NULL, 87 | `StartOutfeed` INT NULL, 88 | `CurrentOutfeed` INT NULL, 89 | `StartWaste` INT NULL, 90 | `CurrentWaste` INT NULL, 91 | `TotalCount` INT NULL, 92 | `WasteCount` INT NULL, 93 | `GoodCount` INT NULL, 94 | `Availability` REAL NULL, 95 | `Performance` REAL NULL, 96 | `Quality` REAL NULL, 97 | `OEE` REAL NULL, 98 | `SetupStartDateTime` DATETIME NULL, 99 | `SetupEndDateTime` DATETIME NULL, 100 | `RunTime` INT NULL, 101 | `UnplannedDowntime` INT NULL, 102 | `PlannedDowntime` INT NULL, 103 | `TotalTime` INT NULL, 104 | `TimeStamp` DATETIME NULL, 105 | `Closed` TINYINT NULL DEFAULT 0, 106 | `EstimatedFinishedTime` DATETIME NULL, 107 | PRIMARY KEY (`ID`) 108 | ); 109 | -------------------------------------------------------------------------------- /Module 3/mes_core.run.py: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | """ 24 | Description: 25 | This module provides functionality to calculate the estimated finish time for a run 26 | based on the production rate, remaining parts, and other parameters defined in the MES system. 27 | 28 | Functions: 29 | - calcFinishTime: Calculates the estimated finish time for a production run. 30 | """ 31 | 32 | #default database name 33 | db = 'mes_core' 34 | 35 | from datetime import datetime, timedelta 36 | 37 | def calcFinishTime(parentPath, db=db): 38 | """ 39 | Calculates the estimated finish time for a production run based on the provided 40 | parent path and database. 41 | 42 | Parameters: 43 | parentPath (str): The path to the parent tag in the MES system. 44 | db (str): The database name to use for the query. Defaults to 'mes_core'. 45 | 46 | Returns: 47 | str: The estimated finish time in ISO format, or 'No Order' if no active run is found. 48 | """ 49 | import system 50 | 51 | # SQL query to retrieve run and schedule information 52 | query = """ 53 | SELECT r.ID AS 'ID', wo.WorkOrder AS 'WorkOrder', r.RunStartDateTime AS 'StartTime', 54 | sch.ScheduleFinishDateTime AS 'FinishTime', sch.Quantity AS 'Quantity' 55 | FROM run r 56 | LEFT JOIN schedule sch ON r.ScheduleID = sch.ID 57 | LEFT JOIN workorder wo ON sch.WorkOrderID = wo.ID 58 | WHERE r.ID = ? AND wo.Closed = 0 59 | """ 60 | 61 | # Retrieve run ID from the tag path 62 | runID = system.tag.readBlocking(["{}/OEE/RunID".format(parentPath)])[0].value 63 | 64 | if runID != -1: 65 | # Query the database for run data 66 | runData = system.db.runPrepQuery(query, [runID], db) 67 | 68 | for row in runData: 69 | quantity = row['Quantity'] 70 | 71 | # Retrieve tag values for good parts and production rate 72 | goodParts = system.tag.readBlocking(["{}/OEE/Good Count".format(parentPath)])[0].value 73 | remainingParts = quantity - goodParts 74 | productionRate = system.tag.readBlocking(["{}/OEE/Production Rate".format(parentPath)])[0].value 75 | 76 | # Calculate remaining hours to complete the production 77 | if productionRate > 0: 78 | hoursRemaining = remainingParts / productionRate 79 | else: 80 | productionRate = 1 81 | hoursRemaining = remainingParts / productionRate 82 | 83 | # Calculate the estimated finish time 84 | currentTime = datetime.now().replace(microsecond=0) 85 | finishTime = currentTime + timedelta(hours=hoursRemaining) 86 | return finishTime.isoformat() 87 | else: 88 | return 'No Order' 89 | 90 | def updateRun(runID, db=db): 91 | ''' 92 | Updates run information based on the given runID. It calculates and updates various run metrics 93 | such as infeed, outfeed, waste, OEE, availability, performance, and quality. 94 | 95 | Parameters: 96 | runID (int): The unique identifier of the run. 97 | db (str): The database name to use for the query. Defaults to 'mes_core'. 98 | 99 | Returns: 100 | int: Returns 1 if the update was successful, or 0 if there was an error. 101 | ''' 102 | from datetime import datetime, timedelta 103 | from java.lang import Exception 104 | from shared.mes_core.logging import log 105 | 106 | try: 107 | # log(f"Starting updateRun process for RunID: {runID}", "info") # Use for debugging 108 | 109 | # Build a query to retrieve LineID and LinePath 110 | lineQuery = """ 111 | SELECT l.ID as 'Line ID', 112 | CONCAT('[mes_core]', e.NAME, '/', s.NAME, '/', a.NAME, '/', l.NAME, '/Line') as 'Line Path' 113 | FROM run r 114 | LEFT JOIN schedule sch ON r.ScheduleID = sch.ID 115 | LEFT JOIN workorder wo ON sch.WorkOrderID = wo.ID 116 | LEFT JOIN line l ON sch.LineID = l.ID 117 | LEFT JOIN area a ON l.ParentID = a.ID 118 | LEFT JOIN site s ON a.ParentID = s.ID 119 | LEFT JOIN enterprise e ON s.ParentID = e.ID 120 | WHERE r.ID = ? 121 | """ 122 | data = system.db.runPrepQuery(lineQuery, [runID], db) 123 | 124 | if not data: 125 | log("No data found for RunID: {}".format(runID), "warn") 126 | return 0 127 | 128 | for row in data: 129 | lineID = row['Line ID'] 130 | linePath = row['Line Path'] 131 | 132 | # log("LineID: {}, LinePath: {}".format(lineID, linePath), "info") # Use for debugging 133 | 134 | # Calculate Finish Time 135 | finishTime = calcFinishTime(linePath) 136 | # log("Calculated FinishTime: {}".format(finishTime), "info") # Use for debugging 137 | 138 | # Get current Timestamp and format 139 | timeStamp = datetime.now() 140 | timeStamp = timeStamp.replace(microsecond=0) 141 | 142 | # List of tag paths 143 | tagPaths = [ 144 | linePath + '/Dispatch/OEE Infeed/Count', 145 | linePath + '/Dispatch/OEE Outfeed/Count', 146 | linePath + '/Dispatch/OEE Waste/Count', 147 | linePath + '/OEE/Total Count', 148 | linePath + '/OEE/Bad Count', 149 | linePath + '/OEE/Good Count', 150 | linePath + '/OEE/Quality', 151 | linePath + '/OEE/Performance', 152 | linePath + '/OEE/Availability', 153 | linePath + '/OEE/Run Time', 154 | linePath + '/OEE/Unplanned Downtime', 155 | linePath + '/OEE/Planned Downtime', 156 | linePath + '/OEE/Total Time' 157 | ] 158 | 159 | # Single call to system.tag.readBlocking 160 | tagValues = system.tag.readBlocking(tagPaths) 161 | 162 | # Assigning values from the tag read results 163 | infeed = tagValues[0].value 164 | outfeed = tagValues[1].value 165 | waste = tagValues[2].value 166 | totalCount = tagValues[3].value 167 | badCount = tagValues[4].value 168 | goodCount = tagValues[5].value 169 | quality = tagValues[6].value 170 | performance = tagValues[7].value 171 | availability = tagValues[8].value 172 | runTime = tagValues[9].value 173 | unplannedDowntime = tagValues[10].value 174 | plannedDowntime = tagValues[11].value 175 | totalTime = tagValues[12].value 176 | 177 | # log("All tag values read successfully.", "info") # Use for debugging 178 | 179 | # Update Run 180 | query = """ 181 | UPDATE run SET 182 | CurrentInfeed = ?, 183 | CurrentOutfeed = ?, 184 | CurrentWaste = ?, 185 | TotalCount = ?, 186 | BadCount = ?, 187 | GoodCount = ?, 188 | Availability = ?, 189 | Performance = ?, 190 | Quality = ?, 191 | OEE = ?, 192 | RunTime = ?, 193 | UnplannedDowntime = ?, 194 | PlannedDowntime = ?, 195 | TotalTime = ?, 196 | TimeStamp = ?, 197 | Closed = ?, 198 | EstimatedFinishTime = ? 199 | WHERE ID = ? 200 | """ 201 | args = [infeed, outfeed, waste, totalCount, badCount, goodCount, availability, 202 | performance, quality, (availability * performance * quality), runTime, 203 | unplannedDowntime, plannedDowntime, totalTime, str(timeStamp), 0, str(finishTime), runID] 204 | 205 | system.db.runPrepUpdate(query, args, db) 206 | # log("Run {} updated successfully.".format(runID), "info") # Use for debugging 207 | return 1 208 | 209 | except Exception as e: 210 | log("Error updating Run {}: {}".format(runID, str(e)), "error") 211 | return 0 212 | -------------------------------------------------------------------------------- /Module 3/mes_core.model.py: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | """ 24 | shared.mes_core.model 25 | 26 | This library provides advanced filtering capabilities for the Unified Namespace (UNS). 27 | It is designed to allow navigation through the UNS, retrieve specific IDs for a given 28 | location, and interact with the backend systems efficiently. 29 | 30 | The MESFilterResults class serves as the core of this library, transforming query results 31 | into a manageable object format with named tuples for quick access to column values. 32 | This is particularly useful for MES systems where navigating and querying large datasets 33 | within the UNS is required. 34 | 35 | Typical use cases: 36 | - Navigate the UNS to locate specific nodes or data points. 37 | - Retrieve IDs or metadata for backend processing. 38 | - Simplify and organize database query results into a structured object format. 39 | 40 | Dependencies: 41 | - This library requires `queryResults` from database queries and the `re` module for 42 | regular expression support (if needed). 43 | """ 44 | 45 | import re 46 | 47 | from mes_core.logging import log 48 | 49 | #default database name 50 | db = 'mes_core' 51 | 52 | class MESFilterResults(object): 53 | """ 54 | A class to transform query results into a more manageable object format with named tuples 55 | for fast access to columns and their values. It allows efficient navigation and retrieval 56 | of specific data within the Unified Namespace (UNS). 57 | """ 58 | 59 | def __init__(self, queryResults): 60 | """ 61 | Initializes the MESFilterResults object by extracting headers and transforming the query 62 | results into ResultEntry objects. 63 | 64 | Args: 65 | queryResults: The results of a database query. 66 | """ 67 | # Create a named tuple for fast object creation 68 | self._headers = [str(c) for c in queryResults.getUnderlyingDataset().getColumnNames()] 69 | 70 | # Nested class for result entry 71 | class ResultEntry(object): 72 | __slots__ = self._headers 73 | 74 | def __init__(self, *args): 75 | self._values = args 76 | self.__dict__.update(zip(self.__slots__, self._values)) 77 | 78 | # Dynamically set getters for each property 79 | for propertyName in self.__slots__: 80 | getterName = "get" + propertyName.capitalize() 81 | getter = lambda self=self, propertyName=propertyName: self.getPropertyValue(propertyName) 82 | setattr(self, getterName, getter) 83 | 84 | def __iter__(self): 85 | return (value for value in self._values) 86 | 87 | def __repr__(self): 88 | return str(dict(zip(self.__slots__, self._values))) 89 | 90 | def __getitem__(self, key): 91 | try: 92 | return self._values[key] 93 | except: 94 | if key in self.__slots__: 95 | return self.__dict__.get(key) 96 | 97 | def getPropertyValue(self, propertyName): 98 | return self.__dict__.get(propertyName) 99 | 100 | # Convert the result set into a list of ResultEntry objects 101 | self.results = [] 102 | for row in queryResults: 103 | self.results.append(ResultEntry(*row)) 104 | 105 | def __iter__(self): 106 | return (result for result in self.results) 107 | 108 | class MESFilter(object): 109 | """ 110 | MESFilter is a utility class designed to provide filtering capabilities for MES objects 111 | within the Unified Namespace (UNS). It supports object types such as 'Enterprise', 'Site', 112 | 'Area', 'Line', and 'Cell', and enforces schema validation during the filtering process. 113 | 114 | This class allows developers to: 115 | - Set and validate MES object types for filtering. 116 | - Enable or restrict searching based on object states. 117 | """ 118 | 119 | # Supported MES object types 120 | mesObjectTypes = ['Enterprise', 'Site', 'Area', 'Line', 'Cell'] 121 | 122 | def __init__(self): 123 | """ 124 | Initializes the MESFilter object with default values. 125 | - enabled: Boolean flag to determine whether filtering is restricted to enabled objects. 126 | - mesObjectType: The type of MES object being filtered (default is None). 127 | """ 128 | self.enabled = True 129 | self.mesObjectType = None 130 | 131 | def setMESObjectTypeName(self, mesObjectType): 132 | """ 133 | Sets the MES object type for filtering and validates its existence in the schema. 134 | 135 | Args: 136 | mesObjectType (str): The MES object type to filter (e.g., 'Enterprise', 'Line'). 137 | 138 | Raises: 139 | ValueError: If the provided object type is not in the supported schema. 140 | """ 141 | if mesObjectType not in self.mesObjectTypes: 142 | raise ValueError("Object type not in model schema") 143 | self.mesObjectType = mesObjectType 144 | 145 | def setEnableStateName(self, stateName='ENABLED'): 146 | """ 147 | Sets the state name to filter enabled or disabled objects. Currently, only enabled 148 | states are supported. 149 | 150 | Args: 151 | stateName (str): The state name to filter by (default is 'ENABLED'). 152 | 153 | Raises: 154 | NotImplementedError: If filtering for disabled objects is attempted. 155 | """ 156 | if stateName != 'ENABLED' and stateName is not True: 157 | raise NotImplementedError("Searching disabled lines is not yet implemented") 158 | 159 | def loadMESObjects(mesFilter, db=db): 160 | """ 161 | Loads MES objects based on the specified filter. 162 | 163 | Args: 164 | mesFilter (MESFilter): The filter object. 165 | db (str): The database name. 166 | 167 | Returns: 168 | MESFilterResults: Filtered results. 169 | """ 170 | try: 171 | results = system.db.runPrepQuery( 172 | loadMESObjectsQueries[mesFilter.mesObjectType], 173 | [], 174 | db 175 | ) 176 | return MESFilterResults(results) 177 | except Exception as e: 178 | log(f"Error loading MES objects: {e}", level='error') 179 | raise 180 | 181 | def getEnterpriseID(modelPath, db=db): 182 | """ 183 | Retrieves the Enterprise ID for a given model path. 184 | 185 | Args: 186 | modelPath (str): Path to the Enterprise in the UNS. 187 | db (str): Database name. 188 | 189 | Returns: 190 | int: The Enterprise ID. 191 | """ 192 | modelParts = re.findall(r"([^/]+)", modelPath) 193 | try: 194 | return system.db.runPrepQuery( 195 | "SELECT e.ID FROM enterprise AS e WHERE e.Name = ?", 196 | [modelParts[0]], 197 | db 198 | )[0][0] 199 | except IndexError: 200 | log(f"Enterprise '{modelParts[0]}' not found.", level='error') 201 | raise ValueError(f"Enterprise '{modelParts[0]}' not found.") 202 | except Exception as e: 203 | log(f"Error in getEnterpriseID: {e}", level='error') 204 | raise 205 | 206 | def getSiteID(modelPath, db=db): 207 | """ 208 | Retrieves the Site ID for a given model path. 209 | 210 | Args: 211 | modelPath (str): Path to the Site in the UNS. 212 | db (str): Database name. 213 | 214 | Returns: 215 | int: The Site ID. 216 | """ 217 | modelParts = re.findall(r"([^/]+)", modelPath) 218 | try: 219 | return system.db.runPrepQuery( 220 | """ 221 | SELECT s.ID 222 | FROM enterprise AS e 223 | INNER JOIN site AS s ON s.ParentID = e.ID 224 | WHERE e.Name = ? AND s.Name = ? 225 | """, 226 | modelParts[:2], 227 | db 228 | )[0][0] 229 | except IndexError: 230 | log(f"Site '{modelParts[1]}' not found under Enterprise '{modelParts[0]}'.", level='error') 231 | raise ValueError(f"Site '{modelParts[1]}' not found under Enterprise '{modelParts[0]}'.") 232 | except Exception as e: 233 | log(f"Error in getSiteID: {e}", level='error') 234 | raise 235 | 236 | def getID(modelPath, db=db): 237 | """ 238 | Retrieves the ID for the specified model path by determining its type. 239 | 240 | Args: 241 | modelPath (str): The model path. 242 | db (str): Database name. 243 | 244 | Returns: 245 | int: The ID for the specified model path. 246 | """ 247 | modelParts = re.findall(r"([^/]+)", modelPath) 248 | modelLookup = { 249 | 1: getEnterpriseID, 250 | 2: getSiteID, 251 | } 252 | try: 253 | return modelLookup[len(modelParts)](modelPath, db=db) 254 | except KeyError: 255 | log(f"Unsupported model path structure: {modelPath}", level='error') 256 | raise NotImplementedError("Only Enterprise and Site are supported.") 257 | except Exception as e: 258 | log(f"Error in getID: {e}", level='error') 259 | raise -------------------------------------------------------------------------------- /Module 4/tags.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Enterprise", 3 | "tagType": "Folder", 4 | "tags": [ 5 | { 6 | "name": "Site", 7 | "tagType": "Folder", 8 | "tags": [ 9 | { 10 | "name": "Area", 11 | "tagType": "Folder", 12 | "tags": [ 13 | { 14 | "name": "Line 1", 15 | "tagType": "Folder", 16 | "tags": [ 17 | { 18 | "name": "Line", 19 | "typeId": "mes_core/Line", 20 | "parameters": { 21 | "line": { 22 | "dataType": "String", 23 | "value": "Line 1" 24 | }, 25 | "lineID": { 26 | "dataType": "Integer", 27 | "value": 1 28 | }, 29 | "Units": { 30 | "dataType": "String", 31 | "value": "parts" 32 | } 33 | }, 34 | "tagType": "UdtInstance", 35 | "tags": [ 36 | { 37 | "name": "Line ID", 38 | "tagType": "AtomicTag" 39 | }, 40 | { 41 | "name": "Collect Data", 42 | "tagType": "AtomicTag" 43 | }, 44 | { 45 | "name": "Cycle Time", 46 | "tagType": "AtomicTag" 47 | }, 48 | { 49 | "name": "State", 50 | "tagType": "AtomicTag" 51 | }, 52 | { 53 | "name": "Infeed", 54 | "tagType": "AtomicTag" 55 | }, 56 | { 57 | "name": "Waste", 58 | "tagType": "AtomicTag" 59 | }, 60 | { 61 | "name": "Outfeed", 62 | "tagType": "AtomicTag" 63 | }, 64 | { 65 | "name": "Run Time", 66 | "tagType": "AtomicTag" 67 | }, 68 | { 69 | "name": "Run Enabled", 70 | "tagType": "AtomicTag" 71 | }, 72 | { 73 | "name": "Rate Setpoint", 74 | "tagType": "AtomicTag" 75 | }, 76 | { 77 | "name": "Dispatch", 78 | "tagType": "UdtInstance", 79 | "tags": [ 80 | { 81 | "name": "Line State", 82 | "tagType": "AtomicTag" 83 | }, 84 | { 85 | "name": "State Start Time", 86 | "tagType": "AtomicTag" 87 | }, 88 | { 89 | "name": "State Reason", 90 | "tagType": "AtomicTag" 91 | }, 92 | { 93 | "name": "Enable", 94 | "tagType": "AtomicTag" 95 | }, 96 | { 97 | "name": "OEE Outfeed", 98 | "tagType": "UdtInstance", 99 | "tags": [ 100 | { 101 | "name": "Enable", 102 | "tagType": "AtomicTag" 103 | }, 104 | { 105 | "name": "TagID", 106 | "tagType": "AtomicTag" 107 | }, 108 | { 109 | "name": "RunID", 110 | "tagType": "AtomicTag" 111 | }, 112 | { 113 | "name": "CountTypeID", 114 | "tagType": "AtomicTag" 115 | }, 116 | { 117 | "name": "Count", 118 | "tagType": "AtomicTag" 119 | }, 120 | { 121 | "name": "LastCount", 122 | "tagType": "AtomicTag" 123 | } 124 | ] 125 | }, 126 | { 127 | "name": "RunID", 128 | "tagType": "AtomicTag" 129 | }, 130 | { 131 | "name": "State Run Time", 132 | "tagType": "AtomicTag" 133 | }, 134 | { 135 | "name": "LineID", 136 | "tagType": "AtomicTag" 137 | }, 138 | { 139 | "name": "OEE Waste", 140 | "tagType": "UdtInstance", 141 | "tags": [ 142 | { 143 | "name": "LastCount", 144 | "tagType": "AtomicTag" 145 | }, 146 | { 147 | "name": "Enable", 148 | "tagType": "AtomicTag" 149 | }, 150 | { 151 | "name": "CountTypeID", 152 | "tagType": "AtomicTag" 153 | }, 154 | { 155 | "name": "Count", 156 | "tagType": "AtomicTag" 157 | }, 158 | { 159 | "name": "TagID", 160 | "tagType": "AtomicTag" 161 | }, 162 | { 163 | "name": "RunID", 164 | "tagType": "AtomicTag" 165 | } 166 | ] 167 | }, 168 | { 169 | "name": "OEE Infeed", 170 | "tagType": "UdtInstance", 171 | "tags": [ 172 | { 173 | "name": "LastCount", 174 | "tagType": "AtomicTag" 175 | }, 176 | { 177 | "name": "RunID", 178 | "tagType": "AtomicTag" 179 | }, 180 | { 181 | "name": "Count", 182 | "tagType": "AtomicTag" 183 | }, 184 | { 185 | "name": "TagID", 186 | "tagType": "AtomicTag" 187 | }, 188 | { 189 | "name": "CountTypeID", 190 | "tagType": "AtomicTag" 191 | }, 192 | { 193 | "name": "Enable", 194 | "tagType": "AtomicTag" 195 | } 196 | ] 197 | } 198 | ] 199 | }, 200 | { 201 | "name": "OEE", 202 | "tagType": "UdtInstance", 203 | "tags": [ 204 | { 205 | "name": "Start Time", 206 | "tagType": "AtomicTag" 207 | }, 208 | { 209 | "name": "Target Count", 210 | "tagType": "AtomicTag" 211 | }, 212 | { 213 | "name": "Run Time", 214 | "tagType": "AtomicTag" 215 | }, 216 | { 217 | "name": "Units", 218 | "tagType": "AtomicTag" 219 | }, 220 | { 221 | "name": "Production Rate", 222 | "tagType": "AtomicTag" 223 | }, 224 | { 225 | "name": "Estimated Finish Time", 226 | "tagType": "AtomicTag" 227 | }, 228 | { 229 | "name": "Quantity", 230 | "tagType": "AtomicTag" 231 | }, 232 | { 233 | "name": "OEE", 234 | "tagType": "AtomicTag" 235 | }, 236 | { 237 | "name": "OEE Quality", 238 | "tagType": "AtomicTag" 239 | }, 240 | { 241 | "name": "Standard Rate", 242 | "tagType": "AtomicTag" 243 | }, 244 | { 245 | "name": "Setup Minutes", 246 | "tagType": "AtomicTag" 247 | }, 248 | { 249 | "name": "Planned Downtime", 250 | "tagType": "AtomicTag" 251 | }, 252 | { 253 | "name": "Schedule ID", 254 | "tagType": "AtomicTag" 255 | }, 256 | { 257 | "name": "OEE Performance", 258 | "tagType": "AtomicTag" 259 | }, 260 | { 261 | "name": "WorkOrder", 262 | "tagType": "AtomicTag" 263 | }, 264 | { 265 | "name": "Total Count", 266 | "tagType": "AtomicTag" 267 | }, 268 | { 269 | "name": "OEE Availability", 270 | "tagType": "AtomicTag" 271 | }, 272 | { 273 | "name": "Scheduled Rate", 274 | "tagType": "AtomicTag" 275 | }, 276 | { 277 | "name": "Total Time", 278 | "tagType": "AtomicTag" 279 | }, 280 | { 281 | "name": "Unplanned Downtime", 282 | "tagType": "AtomicTag" 283 | }, 284 | { 285 | "name": "Bad Count", 286 | "tagType": "AtomicTag" 287 | }, 288 | { 289 | "name": "End Time", 290 | "tagType": "AtomicTag" 291 | }, 292 | { 293 | "name": "Current Time", 294 | "tagType": "AtomicTag" 295 | }, 296 | { 297 | "name": "RunID", 298 | "tagType": "AtomicTag" 299 | }, 300 | { 301 | "name": "Good Count", 302 | "tagType": "AtomicTag" 303 | }, 304 | { 305 | "name": "ID", 306 | "tagType": "AtomicTag" 307 | } 308 | ] 309 | }, 310 | { 311 | "name": "Start Time", 312 | "tagType": "AtomicTag" 313 | }, 314 | { 315 | "name": "Line Name", 316 | "tagType": "AtomicTag" 317 | }, 318 | { 319 | "name": "Manual MES", 320 | "tagType": "AtomicTag" 321 | }, 322 | { 323 | "name": "Current Time", 324 | "tagType": "AtomicTag" 325 | } 326 | ] 327 | } 328 | ] 329 | } 330 | ] 331 | } 332 | ] 333 | } 334 | ] 335 | } -------------------------------------------------------------------------------- /Module 3/mes_core.oee.py: -------------------------------------------------------------------------------- 1 | # 4.0 Solutions LLC CONFIDENTIAL 2 | # 3 | # ___________________________ 4 | # 5 | # [2015] – [2025] 4.0 Solutions LLC 6 | # ALL Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains 9 | # the property of 4.0 Solutions LLC and its suppliers, 10 | # if any. The intellectual and technical concepts contained 11 | # herein are proprietary to 4.0 Solutions LLC 12 | # and its suppliers and may be covered by U.S. and Foreign Patents, 13 | # patents in process, and are protected by trade secret or copyright law. 14 | # 15 | # THIS MATERIAL IS AVAILABLE TO ALL STUDENTS WHO PURCHASED A LICENSE 16 | # TO THE MES BOOTCAMP TRAINING PROGRAM ON IIoT UNIVERSITY BY 4.0 SOLUTIONS. 17 | # LICENSED USERS ARE GRANTED PERMISSION TO USE, MODIFY, AND DISTRIBUTE THIS 18 | # CODE WITHIN THEIR ORGANIZATION OR FOR THEIR CUSTOMERS AS PART OF MES 19 | # SOLUTIONS. USERS MAY MODIFY THE CODE WITHOUT PRIOR APPROVAL, BUT ALL USE 20 | # IS SUBJECT TO THE TERMS OF THE LICENSE AGREEMENT. THIS INCLUDES USAGE FOR 21 | # INTERNAL PROJECTS OR CUSTOMER-DELIVERABLES PROVIDED THROUGH THE LICENSED USER. 22 | 23 | ''' 24 | This script handles OEE (Overall Equipment Effectiveness) calculations for the MES core system. 25 | ''' 26 | 27 | #default database name 28 | db = 'mes_core' 29 | 30 | # Import the logging module 31 | from mes_core.logging import log 32 | 33 | # Log for debugging purposes (commented for production) 34 | # log('MES Core: Calculating OEE', 'info') 35 | 36 | def calcQuality(totalCountPath, goodCountPath, oeeQualityPath): 37 | ''' 38 | Calculates the quality component of OEE. 39 | 40 | Parameters: 41 | - totalCountPath: The tag path for the total count. 42 | - goodCountPath: The tag path for the good count. 43 | - oeeQualityPath: The tag path to store the calculated quality. 44 | 45 | Returns: 46 | - quality (float): The calculated quality percentage. 47 | ''' 48 | # Use a single readBlocking call 49 | tagValues = system.tag.readBlocking([totalCountPath, goodCountPath]) 50 | totalCount = tagValues[0].value 51 | goodCount = tagValues[1].value 52 | 53 | try: 54 | quality = float(goodCount) / float(totalCount) 55 | except: 56 | quality = 1 57 | 58 | system.tag.writeBlocking([oeeQualityPath], [quality]) 59 | return quality 60 | 61 | def calcAvailability(runTimePath, totalTimePath, oeeAvailabilityPath): 62 | ''' 63 | Calculates the availability component of OEE. 64 | 65 | Parameters: 66 | - runTimePath: The tag path for the runtime. 67 | - totalTimePath: The tag path for the total time. 68 | - oeeAvailabilityPath: The tag path to store the calculated availability. 69 | 70 | Returns: 71 | - availability (float): The calculated availability percentage. 72 | ''' 73 | # Use a single readBlocking call 74 | tagValues = system.tag.readBlocking([runTimePath, totalTimePath]) 75 | runTime = tagValues[0].value 76 | totalTime = tagValues[1].value 77 | 78 | try: 79 | availability = float(runTime) / float(totalTime) 80 | except: 81 | availability = 1 82 | 83 | system.tag.writeBlocking([oeeAvailabilityPath], [availability]) 84 | return availability 85 | 86 | def calcPerformance(totalCountPath, targetCountPath, oeePerformancePath): 87 | ''' 88 | Calculates the performance component of OEE. 89 | 90 | Parameters: 91 | - totalCountPath: The tag path for the total count. 92 | - targetCountPath: The tag path for the target count. 93 | - oeePerformancePath: The tag path to store the calculated performance. 94 | 95 | Returns: 96 | - performance (float): The calculated performance percentage. 97 | ''' 98 | # Use a single readBlocking call 99 | tagValues = system.tag.readBlocking([totalCountPath, targetCountPath]) 100 | totalCount = tagValues[0].value 101 | targetCount = tagValues[1].value 102 | 103 | try: 104 | performance = float(totalCount) / float(targetCount) 105 | except: 106 | performance = 1 107 | 108 | system.tag.writeBlocking([oeePerformancePath], [performance]) 109 | return performance 110 | 111 | def getTagIDs(lineID, db=db): 112 | ''' 113 | Retrieves a list of Tag IDs associated with the specified Line ID. 114 | 115 | Parameters: 116 | - lineID (str): The tag path for the Line ID. 117 | - db (str): The database name. Defaults to 'mes_core'. 118 | 119 | Returns: 120 | - listOfTags (list): A list of Tag IDs associated with the given Line ID. 121 | ''' 122 | # Initialize an empty list to hold the tag IDs 123 | listOfTags = [] 124 | 125 | # Read the value of the Line ID tag 126 | id = system.tag.readBlocking([lineID])[0].value 127 | 128 | # Query to retrieve Tag IDs where ParentID matches the Line ID 129 | query = 'SELECT ID FROM counttag WHERE parentID = ?' 130 | data = system.db.runPrepQuery(query, [id], db) 131 | 132 | # Append each Tag ID to the list 133 | for row in data: 134 | tagID = row[0] 135 | listOfTags.append(tagID) 136 | 137 | return listOfTags 138 | 139 | def getGoodCount(lineID, goodCountPath, startTimePath, endTimePath, tagID, countTypeID, db=db): 140 | ''' 141 | Calculates the total good count for a given lineID, tagID, and countTypeID within a specific time range. 142 | Writes the result to the specified goodCountPath. 143 | 144 | Parameters: 145 | - lineID (str): The tag path for the Line ID. 146 | - goodCountPath (str): The tag path where the good count will be written. 147 | - startTimePath (str): The tag path for the start time of the count range. 148 | - endTimePath (str): The tag path for the end time of the count range. 149 | - tagID (int): The ID of the tag being queried. 150 | - countTypeID (int): The type ID of the count being queried. 151 | - db (str): The database name. Defaults to 'mes_core'. 152 | 153 | Returns: 154 | - goodCount (int): The calculated good count. 155 | ''' 156 | try: 157 | # Read all relevant tag values in one call for optimization 158 | tags = [lineID, startTimePath, endTimePath] 159 | tagValues = system.tag.readBlocking(tags) 160 | 161 | # Extract values from the result 162 | id = tagValues[0].value 163 | startTime = tagValues[1].value 164 | endTime = tagValues[2].value 165 | 166 | # Query to calculate the sum of counts from the database 167 | query = """ 168 | SELECT SUM(Count) FROM counthistory 169 | WHERE TagID = ? AND CountTypeID = ? AND TimeStamp BETWEEN ? AND ? 170 | """ 171 | data = system.db.runPrepQuery(query, [tagID, countTypeID, startTime, endTime], db) 172 | 173 | # Default goodCount to 'None' 174 | goodCount = None 175 | 176 | # Process the result 177 | for row in data: 178 | goodCount = row[0] 179 | 180 | # Handle None case 181 | if goodCount is None: 182 | goodCount = 0 183 | 184 | # Write the good count value to the specified path 185 | system.tag.writeBlocking([goodCountPath], [goodCount]) 186 | 187 | return goodCount 188 | except Exception as e: 189 | # Log the exception for debugging 190 | from shared.mes_core.logging import log 191 | log("Error in getGoodCount: {}".format(str(e)), 'error') 192 | return 0 193 | 194 | def getBadCount(badCountPath, startTimePath, endTimePath, tagID, countTypeID, db=db): 195 | """ 196 | Calculates the total bad count for a given tagID and countTypeID within a specific time range. 197 | 198 | Parameters: 199 | badCountPath (str): The tag path where the bad count will be written. 200 | startTimePath (str): The tag path to retrieve the start time. 201 | endTimePath (str): The tag path to retrieve the end time. 202 | tagID (int): The tag identifier to query the count history. 203 | countTypeID (int): The count type identifier to query the count history. 204 | db (str): The database name to use for the query. Defaults to 'mes_core'. 205 | 206 | Returns: 207 | int: The total bad count or -1 if an error occurs. 208 | """ 209 | try: 210 | # Read all required tag values in a single call 211 | tagPaths = [startTimePath, endTimePath] 212 | tagValues = system.tag.readBlocking(tagPaths) 213 | 214 | startTime = tagValues[0].value 215 | endTime = tagValues[1].value 216 | 217 | # Query to calculate bad count 218 | query = """ 219 | SELECT SUM(Count) FROM counthistory 220 | WHERE TagID = ? AND CountTypeID = ? AND TimeStamp BETWEEN ? AND ? 221 | """ 222 | data = system.db.runPrepQuery(query, [tagID, countTypeID, startTime, endTime], db) 223 | 224 | badCount = None 225 | for row in data: 226 | badCount = row[0] 227 | 228 | # Handle cases where no data is returned 229 | if badCount is None: 230 | badCount = 0 231 | 232 | # Write the bad count to the specified tag path 233 | system.tag.writeBlocking([badCountPath], [badCount]) 234 | 235 | return badCount 236 | except Exception as e: 237 | # Log any errors 238 | from shared.mes_core.logging import log 239 | log("Error in getBadCount: {}".format(str(e)), 'error') 240 | return -1 241 | 242 | def getTotalCount(db=db, lineID=None, totalCountPath=None, startTimePath=None, endTimePath=None): 243 | """ 244 | Retrieves the total count of items within a specified time range. 245 | 246 | Args: 247 | db (str): The database name (default: db). 248 | lineID (str): The tag path for the line ID. 249 | totalCountPath (str): The tag path to write the total count. 250 | startTimePath (str): The tag path for the start time. 251 | endTimePath (str): The tag path for the end time. 252 | 253 | Returns: 254 | int: Total count. 255 | """ 256 | # Read all necessary tags in a single call 257 | tagValues = system.tag.readBlocking([lineID, startTimePath, endTimePath]) 258 | lineIDValue = tagValues[0].value 259 | startTime = tagValues[1].value 260 | endTime = tagValues[2].value 261 | 262 | tagIDs = getTagIDs(lineIDValue) 263 | tagIDs = str(tagIDs).replace('[', '(').replace(']', ')').replace(' ', '') 264 | query = ''' 265 | SELECT SUM(Count) FROM counthistory 266 | WHERE countTypeID NOT IN (1, 4) 267 | AND TimeStamp BETWEEN ? AND ? 268 | AND TagID IN %s 269 | ''' % tagIDs 270 | 271 | data = system.db.runPrepQuery(query, [startTime, endTime], db) 272 | totalCount = 0 if not data or data[0][0] is None else data[0][0] 273 | system.tag.writeBlocking([totalCountPath], [totalCount]) 274 | return totalCount 275 | 276 | 277 | def getUnplannedDowntimeSeconds(db=db, startTimePath=None, unplannedDowntimePath=None, lineID=None): 278 | """ 279 | Retrieves the total unplanned downtime in seconds. 280 | 281 | Args: 282 | db (str): The database name (default: db). 283 | startTimePath (str): The tag path for the start time. 284 | unplannedDowntimePath (str): The tag path to write the downtime. 285 | lineID (str): The tag path for the line ID. 286 | 287 | Returns: 288 | int: Unplanned downtime in seconds. 289 | """ 290 | # Read all necessary tags in a single call 291 | tagValues = system.tag.readBlocking([startTimePath, lineID]) 292 | startTime = tagValues[0].value 293 | lineIDValue = tagValues[1].value 294 | 295 | query = ''' 296 | SELECT SUM(TIME_TO_SEC(TIMEDIFF(s.EndDateTime, s.StartDateTime))) AS 'Total in Seconds' 297 | FROM statehistory s 298 | LEFT JOIN statereason st ON s.StateReasonID = st.ID 299 | WHERE st.RecordDowntime = 1 300 | AND s.LineID = ? 301 | AND StartDateTime > ? 302 | AND (EndDateTime <= CURRENT_TIMESTAMP() OR Active = 1) 303 | ''' 304 | data = system.db.runPrepQuery(query, [lineIDValue, startTime], db) 305 | unplannedDowntime = 0 if not data or data[0][0] is None else data[0][0] 306 | system.tag.writeBlocking([unplannedDowntimePath], [unplannedDowntime]) 307 | return unplannedDowntime 308 | 309 | 310 | def getPlannedDowntimeSeconds(db=db, startTimePath=None, plannedDowntimePath=None, lineID=None): 311 | """ 312 | Retrieves the total planned downtime in seconds. 313 | 314 | Args: 315 | db (str): The database name (default: db). 316 | startTimePath (str): The tag path for the start time. 317 | plannedDowntimePath (str): The tag path to write the downtime. 318 | lineID (str): The tag path for the line ID. 319 | 320 | Returns: 321 | int: Planned downtime in seconds. 322 | """ 323 | # Read all necessary tags in a single call 324 | tagValues = system.tag.readBlocking([startTimePath, lineID]) 325 | startTime = tagValues[0].value 326 | lineIDValue = tagValues[1].value 327 | 328 | query = ''' 329 | SELECT SUM(TIME_TO_SEC(TIMEDIFF(s.EndDateTime, s.StartDateTime))) AS 'Total in Seconds' 330 | FROM statehistory s 331 | LEFT JOIN statereason st ON s.StateReasonID = st.ID 332 | WHERE st.PlannedDowntime = 1 333 | AND s.LineID = ? 334 | AND StartDateTime > ? 335 | AND (EndDateTime <= CURRENT_TIMESTAMP() OR Active = 1) 336 | ''' 337 | data = system.db.runPrepQuery(query, [lineIDValue, startTime], db) 338 | plannedDowntime = 0 if not data or data[0][0] is None else data[0][0] 339 | system.tag.writeBlocking([plannedDowntimePath], [plannedDowntime]) 340 | return plannedDowntime 341 | 342 | 343 | def getOee(parentPath): 344 | """ 345 | Retrieves and calculates OEE metrics. 346 | 347 | Args: 348 | parentPath (str): The parent path to the OEE tags. 349 | 350 | Returns: 351 | None 352 | """ 353 | # Declare all tag paths 354 | lineID = f"{parentPath}/OEE/ID" 355 | unplannedDowntimePath = f"{parentPath}/OEE/Unplanned Downtime" 356 | totalTimePath = f"{parentPath}/OEE/Total Time" 357 | totalCountPath = f"{parentPath}/OEE/Total Count" 358 | targetCountPath = f"{parentPath}/OEE/Target Count" 359 | startTimePath = f"{parentPath}/OEE/Start Time" 360 | runTimePath = f"{parentPath}/OEE/Runtime" 361 | plannedDowntimePath = f"{parentPath}/OEE/Planned Downtime" 362 | oeeQualityPath = f"{parentPath}/OEE/OEE Quality" 363 | oeePerformancePath = f"{parentPath}/OEE/OEE Performance" 364 | oeeAvailabilityPath = f"{parentPath}/OEE/OEE Availability" 365 | goodCountPath = f"{parentPath}/OEE/Good Count" 366 | badCountPath = f"{parentPath}/OEE/Bad Count" 367 | 368 | # Perform calculations and write tag updates 369 | getUnplannedDowntimeSeconds(db, startTimePath, unplannedDowntimePath, lineID) 370 | getPlannedDowntimeSeconds(db, startTimePath, plannedDowntimePath, lineID) 371 | calcQuality(totalCountPath, goodCountPath, oeeQualityPath) 372 | calcAvailability(runTimePath, totalTimePath, oeeAvailabilityPath) 373 | calcPerformance(totalCountPath, targetCountPath, oeePerformancePath) 374 | -------------------------------------------------------------------------------- /Module 4/mes_core-udts.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mes_core", 3 | "tagType": "Folder", 4 | "tags": [ 5 | { 6 | "name": "Count Dispatch", 7 | "parameters": { 8 | "lineID": { 9 | "dataType": "Integer", 10 | "value": { 11 | "bindType": "parameter", 12 | "binding": "{lineID}" 13 | } 14 | }, 15 | "dbConnection": { 16 | "dataType": "String", 17 | "value": { 18 | "bindType": "parameter", 19 | "binding": "{dbConnection}" 20 | } 21 | } 22 | }, 23 | "tagType": "UdtType", 24 | "tags": [ 25 | { 26 | "valueSource": "expr", 27 | "expression": "{[.]../RunID}", 28 | "name": "RunID", 29 | "tagType": "AtomicTag" 30 | }, 31 | { 32 | "valueSource": "memory", 33 | "name": "CountTypeID", 34 | "tagType": "AtomicTag" 35 | }, 36 | { 37 | "valueSource": "expr", 38 | "expression": "{[.]../Enable}", 39 | "dataType": "Boolean", 40 | "name": "Enable", 41 | "value": true, 42 | "tagType": "AtomicTag" 43 | }, 44 | { 45 | "valueSource": "memory", 46 | "name": "TagID", 47 | "value": -1, 48 | "tagType": "AtomicTag" 49 | }, 50 | { 51 | "valueSource": "memory", 52 | "name": "LastCount", 53 | "value": 0, 54 | "tagType": "AtomicTag" 55 | }, 56 | { 57 | "valueSource": "expr", 58 | "name": "Count", 59 | "tagType": "AtomicTag" 60 | } 61 | ] 62 | }, 63 | { 64 | "name": "Query Dispatch", 65 | "typeId": "", 66 | "parameters": { 67 | "dbConnection": { 68 | "dataType": "String", 69 | "value": { 70 | "bindType": "parameter", 71 | "binding": "{dbConnection}" 72 | } 73 | }, 74 | "lineID": { 75 | "dataType": "Integer", 76 | "value": { 77 | "bindType": "parameter", 78 | "binding": "{lineID}" 79 | } 80 | } 81 | }, 82 | "tagType": "UdtType", 83 | "tags": [ 84 | { 85 | "name": "OEE Infeed", 86 | "typeId": "mes_core/Count Dispatch", 87 | "tagType": "UdtInstance", 88 | "tags": [ 89 | { 90 | "name": "CountTypeID", 91 | "tagType": "AtomicTag" 92 | }, 93 | { 94 | "name": "Enable", 95 | "tagType": "AtomicTag" 96 | }, 97 | { 98 | "name": "LastCount", 99 | "tagType": "AtomicTag" 100 | }, 101 | { 102 | "name": "TagID", 103 | "tagType": "AtomicTag" 104 | }, 105 | { 106 | "name": "Count", 107 | "tagType": "AtomicTag" 108 | }, 109 | { 110 | "name": "RunID", 111 | "tagType": "AtomicTag" 112 | } 113 | ] 114 | }, 115 | { 116 | "valueSource": "expr", 117 | "name": "Line State", 118 | "tagType": "AtomicTag" 119 | }, 120 | { 121 | "valueSource": "expr", 122 | "expression": "{lineID}", 123 | "name": "LineID", 124 | "tagType": "AtomicTag" 125 | }, 126 | { 127 | "valueSource": "memory", 128 | "dataType": "DateTime", 129 | "name": "State Start Time", 130 | "formatString": "yyyy-MM-dd h:mm:ss aa", 131 | "tagType": "AtomicTag" 132 | }, 133 | { 134 | "valueSource": "expr", 135 | "name": "State Run Time", 136 | "tagType": "AtomicTag", 137 | "engUnit": "Seconds" 138 | }, 139 | { 140 | "valueSource": "memory", 141 | "dataType": "Boolean", 142 | "name": "Enable", 143 | "value": true, 144 | "tagType": "AtomicTag" 145 | }, 146 | { 147 | "valueSource": "expr", 148 | "name": "RunID", 149 | "tagType": "AtomicTag" 150 | }, 151 | { 152 | "name": "OEE Outfeed", 153 | "typeId": "mes_core/Count Dispatch", 154 | "parameters": { 155 | "lineID": { 156 | "dataType": "Integer", 157 | "value": { 158 | "bindType": "parameter", 159 | "binding": "{lineID}" 160 | } 161 | }, 162 | "dbConnection": { 163 | "dataType": "String", 164 | "value": { 165 | "bindType": "parameter", 166 | "binding": "{dbConnection}" 167 | } 168 | } 169 | }, 170 | "tagType": "UdtInstance", 171 | "tags": [ 172 | { 173 | "name": "RunID", 174 | "tagType": "AtomicTag" 175 | }, 176 | { 177 | "name": "LastCount", 178 | "tagType": "AtomicTag" 179 | }, 180 | { 181 | "name": "Count", 182 | "tagType": "AtomicTag" 183 | }, 184 | { 185 | "name": "CountTypeID", 186 | "tagType": "AtomicTag" 187 | }, 188 | { 189 | "name": "TagID", 190 | "tagType": "AtomicTag" 191 | }, 192 | { 193 | "name": "Enable", 194 | "tagType": "AtomicTag" 195 | } 196 | ] 197 | }, 198 | { 199 | "name": "OEE Waste", 200 | "typeId": "mes_core/Count Dispatch", 201 | "parameters": { 202 | "lineID": { 203 | "dataType": "Integer", 204 | "value": { 205 | "bindType": "parameter", 206 | "binding": "{lineID}" 207 | } 208 | }, 209 | "dbConnection": { 210 | "dataType": "String", 211 | "value": { 212 | "bindType": "parameter", 213 | "binding": "{dbConnection}" 214 | } 215 | } 216 | }, 217 | "tagType": "UdtInstance", 218 | "tags": [ 219 | { 220 | "name": "Count", 221 | "tagType": "AtomicTag" 222 | }, 223 | { 224 | "name": "TagID", 225 | "tagType": "AtomicTag" 226 | }, 227 | { 228 | "name": "RunID", 229 | "tagType": "AtomicTag" 230 | }, 231 | { 232 | "name": "LastCount", 233 | "tagType": "AtomicTag" 234 | }, 235 | { 236 | "name": "Enable", 237 | "tagType": "AtomicTag" 238 | }, 239 | { 240 | "name": "CountTypeID", 241 | "tagType": "AtomicTag" 242 | } 243 | ] 244 | }, 245 | { 246 | "datasource": { 247 | "bindType": "parameter", 248 | "binding": "{dbConnection}" 249 | }, 250 | "valueSource": "db", 251 | "dataType": "DataSet", 252 | "name": "State Reason", 253 | "tagType": "AtomicTag" 254 | } 255 | ] 256 | }, 257 | { 258 | "name": "Line", 259 | "parameters": { 260 | "line": { 261 | "dataType": "String" 262 | }, 263 | "lineID": { 264 | "dataType": "Integer" 265 | }, 266 | "dbConnection": { 267 | "dataType": "String", 268 | "value": "mes_core" 269 | }, 270 | "Units": { 271 | "dataType": "String" 272 | } 273 | }, 274 | "tagType": "UdtType", 275 | "tags": [ 276 | { 277 | "name": "Dispatch", 278 | "typeId": "mes_core/Query Dispatch", 279 | "tagType": "UdtInstance", 280 | "tags": [ 281 | { 282 | "name": "RunID", 283 | "tagType": "AtomicTag" 284 | }, 285 | { 286 | "name": "OEE Waste", 287 | "tagType": "UdtInstance", 288 | "tags": [ 289 | { 290 | "name": "RunID", 291 | "tagType": "AtomicTag" 292 | }, 293 | { 294 | "name": "Enable", 295 | "tagType": "AtomicTag" 296 | }, 297 | { 298 | "name": "LastCount", 299 | "tagType": "AtomicTag" 300 | }, 301 | { 302 | "name": "CountTypeID", 303 | "tagType": "AtomicTag" 304 | }, 305 | { 306 | "name": "Count", 307 | "tagType": "AtomicTag" 308 | }, 309 | { 310 | "name": "TagID", 311 | "tagType": "AtomicTag" 312 | } 313 | ] 314 | }, 315 | { 316 | "name": "Line State", 317 | "tagType": "AtomicTag" 318 | }, 319 | { 320 | "name": "State Start Time", 321 | "tagType": "AtomicTag" 322 | }, 323 | { 324 | "name": "Enable", 325 | "tagType": "AtomicTag" 326 | }, 327 | { 328 | "name": "LineID", 329 | "tagType": "AtomicTag" 330 | }, 331 | { 332 | "name": "OEE Outfeed", 333 | "tagType": "UdtInstance", 334 | "tags": [ 335 | { 336 | "name": "Count", 337 | "tagType": "AtomicTag" 338 | }, 339 | { 340 | "name": "TagID", 341 | "tagType": "AtomicTag" 342 | }, 343 | { 344 | "name": "RunID", 345 | "tagType": "AtomicTag" 346 | }, 347 | { 348 | "name": "Enable", 349 | "tagType": "AtomicTag" 350 | }, 351 | { 352 | "name": "CountTypeID", 353 | "tagType": "AtomicTag" 354 | }, 355 | { 356 | "name": "LastCount", 357 | "tagType": "AtomicTag" 358 | } 359 | ] 360 | }, 361 | { 362 | "name": "State Run Time", 363 | "tagType": "AtomicTag" 364 | }, 365 | { 366 | "name": "OEE Infeed", 367 | "tagType": "UdtInstance", 368 | "tags": [ 369 | { 370 | "name": "Enable", 371 | "tagType": "AtomicTag" 372 | }, 373 | { 374 | "name": "LastCount", 375 | "tagType": "AtomicTag" 376 | }, 377 | { 378 | "name": "RunID", 379 | "tagType": "AtomicTag" 380 | }, 381 | { 382 | "name": "Count", 383 | "tagType": "AtomicTag" 384 | }, 385 | { 386 | "name": "TagID", 387 | "tagType": "AtomicTag" 388 | }, 389 | { 390 | "name": "CountTypeID", 391 | "tagType": "AtomicTag" 392 | } 393 | ] 394 | }, 395 | { 396 | "name": "State Reason", 397 | "tagType": "AtomicTag" 398 | } 399 | ] 400 | }, 401 | { 402 | "valueSource": "expr", 403 | "name": "Outfeed", 404 | "tagType": "AtomicTag" 405 | }, 406 | { 407 | "valueSource": "memory", 408 | "dataType": "Boolean", 409 | "name": "Manual MES", 410 | "value": false, 411 | "tagType": "AtomicTag" 412 | }, 413 | { 414 | "valueSource": "memory", 415 | "name": "Waste", 416 | "tagType": "AtomicTag" 417 | }, 418 | { 419 | "valueSource": "memory", 420 | "dataType": "Boolean", 421 | "name": "Collect Data", 422 | "value": false, 423 | "tagType": "AtomicTag" 424 | }, 425 | { 426 | "valueSource": "memory", 427 | "name": "Rate Setpoint", 428 | "tagType": "AtomicTag" 429 | }, 430 | { 431 | "valueSource": "expr", 432 | "expression": "{line}", 433 | "dataType": "String", 434 | "name": "Line Name", 435 | "tagType": "AtomicTag" 436 | }, 437 | { 438 | "valueSource": "expr", 439 | "name": "Run Time", 440 | "tagType": "AtomicTag", 441 | "engUnit": "Seconds" 442 | }, 443 | { 444 | "valueSource": "expr", 445 | "expression": "now()", 446 | "dataType": "DateTime", 447 | "name": "Current Time", 448 | "formatString": "yyyy-MM-dd h:mm:ss aa", 449 | "tagType": "AtomicTag" 450 | }, 451 | { 452 | "valueSource": "expr", 453 | "name": "State", 454 | "tagType": "AtomicTag" 455 | }, 456 | { 457 | "valueSource": "memory", 458 | "dataType": "DateTime", 459 | "name": "Start Time", 460 | "formatString": "yyyy-MM-dd h:mm:ss aa", 461 | "tagType": "AtomicTag" 462 | }, 463 | { 464 | "valueSource": "memory", 465 | "dataType": "Boolean", 466 | "name": "Run Enabled", 467 | "value": false, 468 | "tagType": "AtomicTag" 469 | }, 470 | { 471 | "name": "OEE", 472 | "typeId": "mes_core/OEE-Downtime", 473 | "tagType": "UdtInstance", 474 | "tags": [ 475 | { 476 | "name": "Planned Downtime", 477 | "tagType": "AtomicTag" 478 | }, 479 | { 480 | "name": "Total Count", 481 | "tagType": "AtomicTag" 482 | }, 483 | { 484 | "name": "Scheduled Rate", 485 | "tagType": "AtomicTag" 486 | }, 487 | { 488 | "name": "Bad Count", 489 | "tagType": "AtomicTag" 490 | }, 491 | { 492 | "name": "Schedule ID", 493 | "tagType": "AtomicTag" 494 | }, 495 | { 496 | "name": "Setup Minutes", 497 | "tagType": "AtomicTag" 498 | }, 499 | { 500 | "name": "OEE Performance", 501 | "tagType": "AtomicTag" 502 | }, 503 | { 504 | "name": "Current Time", 505 | "tagType": "AtomicTag" 506 | }, 507 | { 508 | "name": "Production Rate", 509 | "tagType": "AtomicTag" 510 | }, 511 | { 512 | "name": "RunID", 513 | "tagType": "AtomicTag" 514 | }, 515 | { 516 | "name": "OEE", 517 | "tagType": "AtomicTag" 518 | }, 519 | { 520 | "name": "Target Count", 521 | "tagType": "AtomicTag" 522 | }, 523 | { 524 | "name": "Good Count", 525 | "tagType": "AtomicTag" 526 | }, 527 | { 528 | "name": "Run Time", 529 | "tagType": "AtomicTag" 530 | }, 531 | { 532 | "name": "ID", 533 | "tagType": "AtomicTag" 534 | }, 535 | { 536 | "name": "Estimated Finish Time", 537 | "tagType": "AtomicTag" 538 | }, 539 | { 540 | "name": "End Time", 541 | "tagType": "AtomicTag" 542 | }, 543 | { 544 | "name": "OEE Quality", 545 | "tagType": "AtomicTag" 546 | }, 547 | { 548 | "name": "Total Time", 549 | "tagType": "AtomicTag" 550 | }, 551 | { 552 | "name": "Quantity", 553 | "tagType": "AtomicTag" 554 | }, 555 | { 556 | "name": "Unplanned Downtime", 557 | "tagType": "AtomicTag" 558 | }, 559 | { 560 | "name": "Start Time", 561 | "tagType": "AtomicTag" 562 | }, 563 | { 564 | "name": "Standard Rate", 565 | "tagType": "AtomicTag" 566 | }, 567 | { 568 | "name": "OEE Availability", 569 | "tagType": "AtomicTag" 570 | }, 571 | { 572 | "name": "Units", 573 | "tagType": "AtomicTag" 574 | }, 575 | { 576 | "name": "WorkOrder", 577 | "tagType": "AtomicTag" 578 | } 579 | ] 580 | }, 581 | { 582 | "valueSource": "expr", 583 | "dataType": "Float4", 584 | "name": "Cycle Time", 585 | "tagType": "AtomicTag" 586 | }, 587 | { 588 | "valueSource": "expr", 589 | "expression": "{lineID}", 590 | "name": "Line ID", 591 | "tagType": "AtomicTag" 592 | }, 593 | { 594 | "valueSource": "expr", 595 | "name": "Infeed", 596 | "tagType": "AtomicTag" 597 | } 598 | ] 599 | }, 600 | { 601 | "name": "OEE-Downtime", 602 | "parameters": { 603 | "lineID": { 604 | "dataType": "Integer", 605 | "value": { 606 | "bindType": "parameter", 607 | "binding": "{lineID}" 608 | } 609 | }, 610 | "dbConnection": { 611 | "dataType": "String", 612 | "value": { 613 | "bindType": "parameter", 614 | "binding": "{dbConnection}" 615 | } 616 | } 617 | }, 618 | "tagType": "UdtType", 619 | "tags": [ 620 | { 621 | "valueSource": "memory", 622 | "name": "Unplanned Downtime", 623 | "value": 0, 624 | "tagType": "AtomicTag" 625 | }, 626 | { 627 | "valueSource": "expr", 628 | "name": "Schedule ID", 629 | "tagType": "AtomicTag" 630 | }, 631 | { 632 | "datasource": { 633 | "bindType": "parameter", 634 | "binding": "{dbConnection}" 635 | }, 636 | "valueSource": "db", 637 | "dataType": "DataSet", 638 | "name": "WorkOrder", 639 | "tagType": "AtomicTag" 640 | }, 641 | { 642 | "valueSource": "expr", 643 | "name": "Total Time", 644 | "tagType": "AtomicTag", 645 | "engUnit": "Seconds" 646 | }, 647 | { 648 | "valueSource": "memory", 649 | "name": "RunID", 650 | "value": -1, 651 | "tagType": "AtomicTag" 652 | }, 653 | { 654 | "valueSource": "memory", 655 | "name": "Good Count", 656 | "value": 0, 657 | "tagType": "AtomicTag" 658 | }, 659 | { 660 | "valueSource": "expr", 661 | "name": "Scheduled Rate", 662 | "tagType": "AtomicTag" 663 | }, 664 | { 665 | "valueSource": "memory", 666 | "dataType": "Float4", 667 | "name": "OEE Performance", 668 | "value": 0, 669 | "tagType": "AtomicTag" 670 | }, 671 | { 672 | "valueSource": "memory", 673 | "dataType": "Float4", 674 | "name": "OEE", 675 | "value": 0, 676 | "tagType": "AtomicTag" 677 | }, 678 | { 679 | "valueSource": "expr", 680 | "dataType": "DateTime", 681 | "name": "Estimated Finish Time", 682 | "formatString": "yyyy-MM-dd h:mm:ss aa", 683 | "tagType": "AtomicTag" 684 | }, 685 | { 686 | "valueSource": "memory", 687 | "dataType": "String", 688 | "name": "Units", 689 | "tagType": "AtomicTag" 690 | }, 691 | { 692 | "valueSource": "expr", 693 | "name": "Production Rate", 694 | "tagType": "AtomicTag" 695 | }, 696 | { 697 | "datasource": { 698 | "bindType": "parameter", 699 | "binding": "{dbConnection}" 700 | }, 701 | "valueSource": "db", 702 | "name": "Start Time", 703 | "tagType": "AtomicTag" 704 | }, 705 | { 706 | "valueSource": "memory", 707 | "dataType": "Float4", 708 | "name": "OEE Quality", 709 | "value": 0, 710 | "tagType": "AtomicTag" 711 | }, 712 | { 713 | "valueSource": "expr", 714 | "name": "Quantity", 715 | "tagType": "AtomicTag" 716 | }, 717 | { 718 | "valueSource": "expr", 719 | "name": "Target Count", 720 | "tagType": "AtomicTag" 721 | }, 722 | { 723 | "valueSource": "memory", 724 | "dataType": "Float4", 725 | "name": "OEE Availability", 726 | "value": 0, 727 | "tagType": "AtomicTag" 728 | }, 729 | { 730 | "valueSource": "memory", 731 | "name": "Standard Rate", 732 | "tagType": "AtomicTag" 733 | }, 734 | { 735 | "valueSource": "memory", 736 | "name": "Total Count", 737 | "value": 0, 738 | "tagType": "AtomicTag" 739 | }, 740 | { 741 | "valueSource": "memory", 742 | "name": "Planned Downtime", 743 | "value": 0, 744 | "tagType": "AtomicTag", 745 | "engUnit": "Seconds" 746 | }, 747 | { 748 | "valueSource": "expr", 749 | "name": "Setup Minutes", 750 | "tagType": "AtomicTag" 751 | }, 752 | { 753 | "valueSource": "expr", 754 | "name": "ID", 755 | "tagType": "AtomicTag" 756 | }, 757 | { 758 | "valueSource": "expr", 759 | "name": "Run Time", 760 | "tagType": "AtomicTag", 761 | "engUnit": "Seconds" 762 | }, 763 | { 764 | "valueSource": "expr", 765 | "expression": "now()\r\n", 766 | "dataType": "DateTime", 767 | "name": "Current Time", 768 | "formatString": "yyyy-MM-dd h:mm:ss aa", 769 | "tagType": "AtomicTag" 770 | }, 771 | { 772 | "valueSource": "expr", 773 | "dataType": "DateTime", 774 | "name": "End Time", 775 | "formatString": "yyyy-MM-dd h:mm:ss aa", 776 | "tagType": "AtomicTag" 777 | }, 778 | { 779 | "valueSource": "memory", 780 | "name": "Bad Count", 781 | "value": 0, 782 | "tagType": "AtomicTag" 783 | } 784 | ] 785 | } 786 | ] 787 | } --------------------------------------------------------------------------------