Explorar o código

Add dual inventory system so accurate visual updates of the HBW inventory are possible

anfeny hai 8 meses
pai
achega
54724eacd4

+ 71 - 0
dashboard/src/static/js/sim/components/hbw-inventory.js

@@ -0,0 +1,71 @@
+import { Crate } from './crate.js';
+import { Workpiece } from './workpiece.js';
+
+
+export class WarehouseInventory {
+    constructor(x, y, height, width = 30) {
+        this.x = x;
+        this.y = y;
+        this.w = width;
+        this.h = height;
+
+        // Create inventory of crates, initially empty
+        this.crateDict = {};
+        const rows = ['A', 'B', 'C'];
+        const cols = [1, 2, 3];
+
+        for (const row of rows) {
+            for (const col of cols) {
+                const key = `${row}${col}`;
+                const pos = this.getTargetPos(key);
+                this.crateDict[key] = new Crate(pos.x, pos.y);
+            }
+        }
+    }
+
+    draw() {
+        push();
+        fill('black');
+        rect(this.x, this.y, this.w, this.h);
+
+        // Draw the top row
+        for (let loc of ["A1", "A2", "A3"]) {
+            this.crateDict[loc].draw();
+        }
+        pop();
+    }
+
+    updateInventory(updateMessage) {
+        // Update the inventory based on the update message
+        for (const item of updateMessage.stockItems) {
+            const location = item.location;
+            const workpiece = item.workpiece;
+
+            if (this.crateDict[location] && workpiece !== null) {
+                this.crateDict[location].addWorkpiece(new Workpiece(0,0, workpiece.type));
+            }
+            else{
+                this.crateDict[location].wp = null;
+            }
+        }
+    }
+
+    /**
+     * Get the position of the row with a certain name
+     * targetName can be "A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"
+     * @param {string} targetName 
+     * @returns {object} {x, y} position of the column
+     */
+    getTargetPos(targetName) {
+        if (targetName.includes("1")) {
+            return { x: this.x + this.w / 2, y: this.y + (this.h / 3 / 2) + 2 * this.h / 3 };
+        }
+        if (targetName.includes("2")) {
+            return { x: this.x + this.w / 2, y: this.y + (this.h / 3 / 2) + this.h / 3  };
+        }
+        if (targetName.includes("3")) {
+            return { x: this.x + this.w / 2, y: this.y + (this.h / 3 / 2) };
+        }
+        return null;
+    }
+}

+ 0 - 61
dashboard/src/static/js/sim/components/inventory.js

@@ -1,61 +0,0 @@
-import { Crate } from './crate.js';
-
-
-export class WarehouseInventory {
-    constructor(x, y, height, width=30) {
-        this.x = x;
-        this.y = y;
-        this.w = width;
-        this.h = height;
-        this.crates = [
-            new Crate(x+width/2, y + (height/3/2)),
-            new Crate(x+width/2, y + (height/3/2) + height/3),
-            new Crate(x+width/2, y + (height/3/2) + 2*height/3)
-        ];
-    }
-
-    draw() {
-        push();
-        fill('black');
-        rect(this.x, this.y, this.w, this.h);
-
-        for (let i = 0; i < this.crates.length; i++) {
-            this.crates[i].draw();
-        }
-        pop();
-    }
-
-    /**
-     * Get column 1 (lowest (left-most) column)
-     */
-    getCol1(){
-        return {x: this.crates[2].x, y: this.crates[2].y};
-    }
-
-    getCol2(){
-        return {x: this.crates[1].x, y: this.crates[1].y};
-    }
-
-    getCol3(){
-        return {x: this.crates[0].x, y: this.crates[0].y};
-    }
-
-    /**
-     * Get the position of the row with a certain name
-     * targetName can be "A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"
-     * @param {string} targetName 
-     * @returns {object} {x, y} position of the column
-     */
-    getTargetPos(targetName){
-        if (targetName.includes("1")) {
-            return this.getCol1();
-        }
-        if (targetName.includes("2")) {
-            return this.getCol2();
-        }
-        if (targetName.includes("3")) {
-            return this.getCol3();
-        }
-        return null;
-    }
-}

+ 1 - 1
dashboard/src/static/js/sim/factory.js

@@ -11,7 +11,7 @@ import { DeliveryStation } from './components/delivery-station.js';
 import { CrateTransporter } from './components/crate-transporter.js';
 import { Vgr } from './components/vgr.js';
 import { Hbw } from './components/hbw.js';
-import { WarehouseInventory } from './components/inventory.js';
+import { WarehouseInventory } from './components/hbw-inventory.js';
 import { Sld } from './components/sld.js';
 
 export class Factory {

+ 5 - 0
dashboard/src/static/js/sim/index.js

@@ -116,6 +116,11 @@ socket.on('visualization_update', function (data) {
         }
     }
 
+    // HBW STOCK (INVENTORY)
+    else if (data.topic.includes("stock")) {
+        factory.inventory.updateInventory(msg);
+    }
+
     // CONVEYOR
     if (data.topic.includes("conveyor")) {
         factory.simpleConveyor.setWorkpiece(workpiece); // update workpiece

+ 5 - 0
simulator/data_models/warehouse_inventory.py

@@ -62,6 +62,11 @@ class WarehouseInventory:
             for location, crate in self.inventory.items()
         ]
         return message
+    
+    def get_visual_update_data(self) -> MqttMessage:
+        message = self.get_stock_update_message()
+        message.topic = "visualization/stock"
+        return message
 
     def get_loc_free_slot(self) -> str | None:
         """ Get the location of the next free slot in the inventory, None if full"""

+ 1 - 1
simulator/devs_models/fischertechnik_factory.py

@@ -77,4 +77,4 @@ class FischertechnikFactory(CoupledDEVS):
 
     def get_inventory(self) -> dict:
         """ Returns the inventory of the system """
-        return self.hbw.state.inventory.inventory
+        return self.hbw.state.mqtt_inventory.inventory

+ 20 - 16
simulator/devs_models/high_bay_warehouse.py

@@ -103,7 +103,8 @@ LOCATION_TIMINGS: dict = {
 @dataclass
 class WarehouseState:
     current_item: Crate | None = None
-    inventory: WarehouseInventory = WarehouseInventory()
+    mqtt_inventory: WarehouseInventory = WarehouseInventory() # Inventory that is indicative of the future (=not accurate to real state, but accurate to factory output)
+    visual_inventory: WarehouseInventory = WarehouseInventory() # Inventory that is accurate to the simulation (-> visual updates)
     phase: HbwPhase = HbwPhase.IDLE
     target: WorkpieceColor | str = "" # workpiece color or 'empty' to denote target is an empty crate
     target_location: str = "" # location of the target in the inventory
@@ -223,9 +224,9 @@ class HighBayWarehouse(AtomicDEVS):
             else:
                 self.change_phase(HbwPhase.CMD_STORE_CRATE)
                 logger.trace(f"{type(self).__name__} '{self.name}' received: {new_crate}")
-                self.state.target_location = self.state.inventory.get_loc_empty_crate()
+                self.state.target_location = self.state.mqtt_inventory.get_loc_free_slot()
                 # if we have a new workpiece, immediately store it
-                self._store_crate(self.state.current_item) # TODO: check if matches with mqtt
+                self._store_crate(self.state.current_item, self.state.mqtt_inventory) # TODO: check if matches with mqtt
 
         elif self.mqtt_in in inputs:
             mqtt_msg: MqttMessage = inputs[self.mqtt_in][0]
@@ -233,11 +234,11 @@ class HighBayWarehouse(AtomicDEVS):
                 if mqtt_msg.payload['code'] == 1: # FETCH EMPTY
                     self.change_phase(HbwPhase.CMD_FETCH_CRATE)
                     self.state.target = "empty"
-                    self.state.target_location = self.state.inventory.get_loc_empty_crate()
+                    self.state.target_location = self.state.mqtt_inventory.get_loc_empty_crate()
                 elif mqtt_msg.payload['code'] == 3: # FETCH WP
                     self.change_phase(HbwPhase.CMD_FETCH_CRATE)
                     self.state.target = WorkpieceColor(mqtt_msg.payload['workpiece']['type'])
-                    self.state.target_location = self.state.inventory.get_loc_workpiece_crate(color=self.state.target)
+                    self.state.target_location = self.state.mqtt_inventory.get_loc_workpiece_crate(color=self.state.target)
             elif mqtt_msg.topic == "simulation/get_status":
                 self.state.status_requested = True
         return self.state  # important, return state
@@ -249,7 +250,7 @@ class HighBayWarehouse(AtomicDEVS):
 
     def outputFnc(self):
         if self.state.visual_update_pending:
-            return {self.mqtt_out: [self.get_visual_update_data()]}
+            return {self.mqtt_out: [self.get_visual_update_data(), self.state.visual_inventory.get_visual_update_data()]}
         if self.state.status_requested:
             return {self.mqtt_out: [self.get_status_message(use_next_phase=False)]}
 
@@ -257,22 +258,22 @@ class HighBayWarehouse(AtomicDEVS):
             return {self.out: [self.state.current_item], self.mqtt_out: [self.get_status_message()]}
 
         # else: update the inventory data and status
-        return {self.inventory_out: [self.state.inventory], self.mqtt_out: [self.get_status_message()]}
+        return {self.inventory_out: [self.state.mqtt_inventory], self.mqtt_out: [self.get_status_message()]}
 
-    def _get_crate(self, target: WorkpieceColor | str) -> Crate | None:
+    def _get_crate(self, target: WorkpieceColor | str, inventory: WarehouseInventory) -> Crate | None:
         """Get crate, depending on target"""
         if target == "":
             logger.error(f"{type(self).__name__} '{self.name}' attempting to retrieve crate: {target}")
             return None
         elif target == "empty":
-            empty_loc = self.state.inventory.get_loc_empty_crate()
-            return self.state.inventory.take(empty_loc)
+            empty_loc = inventory.get_loc_empty_crate()
+            return inventory.take(empty_loc)
         else:
-            wp_loc = self.state.inventory.get_loc_workpiece_crate(color=target)
-            return self.state.inventory.take(wp_loc)
+            wp_loc = inventory.get_loc_workpiece_crate(color=target)
+            return inventory.take(wp_loc)
 
-    def _store_crate(self, crate: Crate) -> None:
-        self.state.inventory.insert(crate)
+    def _store_crate(self, crate: Crate, inventory: WarehouseInventory) -> None:
+        inventory.insert(crate)
 
     def intTransition(self):
         if self.state.visual_update_pending:
@@ -285,13 +286,16 @@ class HighBayWarehouse(AtomicDEVS):
 
         if self.state.phase == HbwPhase.FETCH_CRATE:
             # Get the appropriate crate
-            self.state.current_item = self._get_crate(target=self.state.target)
-
+            self.state.current_item = self._get_crate(target=self.state.target, inventory=self.state.mqtt_inventory)
+            # Also update the visual inventory
+            self._get_crate(self.state.target, self.state.visual_inventory)
+            
         elif self.state.phase == HbwPhase.PUSH_ONTO_CT:
             # if we just output a crate, remove it
             self.state.current_item = None
 
         elif self.state.phase == HbwPhase.STORE_CRATE:
+            self._store_crate(crate=self.state.current_item, inventory=self.state.visual_inventory) # Store crate visually
             self.state.current_item = None # remove stored crate
 
         elif self.state.phase == HbwPhase.MOVING_TO_REST_POS:

+ 22 - 22
simulator/flowchart.md

@@ -21,11 +21,11 @@ flowchart LR
 	DSI_out --> VacuumGripper_dsi_in
 	DSI_mqtt_out --> MQTTControlUnit_mqtt_in
 	subgraph ReaderStation
-		ReaderStation_nfc_out("nfc_out")
-		ReaderStation_color_out("color_out")
 		ReaderStation_mqtt_out("mqtt_out")
+		ReaderStation_nfc_out("nfc_out")
 		ReaderStation_color_in("color_in")
 		ReaderStation_nfc_in("nfc_in")
+		ReaderStation_color_out("color_out")
 		ReaderStation_nfc_in ~~~ ReaderStation_nfc_out
 		ReaderStation_color_in ~~~ ReaderStation_color_out
 	end
@@ -33,24 +33,24 @@ flowchart LR
 	ReaderStation_color_out --> VacuumGripper_color_sensor_in
 	ReaderStation_mqtt_out --> MQTTControlUnit_mqtt_in
 	subgraph VacuumGripper
-		VacuumGripper_sld_blue_in("sld_blue_in")
-		VacuumGripper_nfc_out("nfc_out")
-		VacuumGripper_sld_white_in("sld_white_in")
 		VacuumGripper_sld_red_in("sld_red_in")
-		VacuumGripper_dsi_in("dsi_in")
-		VacuumGripper_color_sensor_out("color_sensor_out")
-		VacuumGripper_nfc_in("nfc_in")
-		VacuumGripper_mpo_out("mpo_out")
-		VacuumGripper_sld_red_out("sld_red_out")
-		VacuumGripper_ct_in("ct_in")
 		VacuumGripper_sld_blue_out("sld_blue_out")
-		VacuumGripper_ct_out("ct_out")
-		VacuumGripper_dso_out("dso_out")
-		VacuumGripper_sld_white_out("sld_white_out")
 		VacuumGripper_color_sensor_in("color_sensor_in")
-		VacuumGripper_dsi_out("dsi_out")
+		VacuumGripper_nfc_out("nfc_out")
+		VacuumGripper_ct_out("ct_out")
 		VacuumGripper_mqtt_out("mqtt_out")
+		VacuumGripper_sld_red_out("sld_red_out")
+		VacuumGripper_nfc_in("nfc_in")
+		VacuumGripper_ct_in("ct_in")
 		VacuumGripper_mqtt_in("mqtt_in")
+		VacuumGripper_color_sensor_out("color_sensor_out")
+		VacuumGripper_dso_out("dso_out")
+		VacuumGripper_dsi_in("dsi_in")
+		VacuumGripper_sld_white_in("sld_white_in")
+		VacuumGripper_mpo_out("mpo_out")
+		VacuumGripper_dsi_out("dsi_out")
+		VacuumGripper_sld_white_out("sld_white_out")
+		VacuumGripper_sld_blue_in("sld_blue_in")
 		VacuumGripper_dsi_in ~~~ VacuumGripper_dsi_out
 		VacuumGripper_ct_in ~~~ VacuumGripper_ct_out
 		VacuumGripper_nfc_in ~~~ VacuumGripper_nfc_out
@@ -71,11 +71,11 @@ flowchart LR
 	VacuumGripper_dso_out --> DSO_inp
 	VacuumGripper_mqtt_out --> MQTTControlUnit_mqtt_in
 	subgraph Transporter
-		Transporter_right_out("right_out")
 		Transporter_left_out("left_out")
 		Transporter_mqtt_out("mqtt_out")
 		Transporter_left_in("left_in")
 		Transporter_right_in("right_in")
+		Transporter_right_out("right_out")
 		Transporter_left_in ~~~ Transporter_right_out
 		Transporter_right_in ~~~ Transporter_left_out
 	end
@@ -150,14 +150,14 @@ flowchart LR
 	Conveyor_out --> SLD_inp
 	Conveyor_mqtt_out --> MQTTControlUnit_mqtt_in
 	subgraph SLD
+		SLD_blue_out("blue_out")
+		SLD_mqtt_out("mqtt_out")
+		SLD_inp("inp")
 		SLD_white_in("white_in")
 		SLD_red_in("red_in")
 		SLD_blue_in("blue_in")
 		SLD_white_out("white_out")
 		SLD_red_out("red_out")
-		SLD_blue_out("blue_out")
-		SLD_mqtt_out("mqtt_out")
-		SLD_inp("inp")
 		SLD_inp ~~~ SLD_white_out
 		SLD_white_in ~~~ SLD_red_out
 		SLD_red_in ~~~ SLD_blue_out
@@ -186,8 +186,8 @@ flowchart LR
 		SLD_white_bay_mqtt_out --> SLD_mqtt_out
 		subgraph SLD_red_bay
 			SLD_red_bay_vgr_out("vgr_out")
-			SLD_red_bay_sld_in("sld_in")
 			SLD_red_bay_mqtt_out("mqtt_out")
+			SLD_red_bay_sld_in("sld_in")
 			SLD_red_bay_vgr_in("vgr_in")
 			SLD_red_bay_sld_in ~~~ SLD_red_bay_vgr_out
 			SLD_red_bay_vgr_in ~~~ SLD_red_bay_mqtt_out
@@ -214,10 +214,10 @@ flowchart LR
 	SLD_red_in --> SLD_red_bay_vgr_in
 	SLD_blue_in --> SLD_blue_bay_vgr_in
 	subgraph DSO
+		DSO_inp("inp")
 		DSO_mqtt_out("mqtt_out")
 		DSO_mqtt_in("mqtt_in")
 		DSO_out("out")
-		DSO_inp("inp")
 		DSO_inp ~~~ DSO_out
 		DSO_mqtt_in ~~~ DSO_mqtt_out
 	end
@@ -225,8 +225,8 @@ flowchart LR
 	subgraph MQTTControlUnit
 		MQTTControlUnit_REALTIME_INTERRUPT("REALTIME_INTERRUPT")
 		MQTTControlUnit_mqtt_in("mqtt_in")
-		MQTTControlUnit_mqtt_out("mqtt_out")
 		MQTTControlUnit_REALTIME_OBSERVED("REALTIME_OBSERVED")
+		MQTTControlUnit_mqtt_out("mqtt_out")
 		MQTTControlUnit_mqtt_in ~~~ MQTTControlUnit_mqtt_out
 		MQTTControlUnit_REALTIME_INTERRUPT ~~~ MQTTControlUnit_REALTIME_OBSERVED
 	end