;;;; -*- Mode:COMMON-LISP; Package:ELEVATOR-EXAMPLE; Base:10 -*- ;;;; *-* File: lapis: /u7/gbb/v-400/dev/source/gbb/examples/elevator-example.lisp *-* ;;;; *-* Edited-By: Cork *-* ;;;; *-* Last-Edit: Friday, September 18, 1998 14:31:34 *-* ;;;; *-* Machine: GRANITE (Explorer II, Microcode 489) *-* ;;;; ************************************************************************** ;;;; ************************************************************************** ;;;; * ;;;; * ELEVATOR CONTROLLER/SIMULATION PROBLEM ;;;; * ;;;; ************************************************************************** ;;;; ************************************************************************** ;;; ;;; Written by: Participants in ;;; ``Designing and Implementing Blackboard Applications'' ;;; Amherst, Massachusetts, March 25-27, 1992 ;;; ;;; ------------------------------------------------------------------------ ;;; To run, evaluate (elevator:elevator-example). ;;; ;;; Tip: Use GBB's graphics to see what is happening! ;;; ;;; ------------------------------------------------------------------------ ;;; Overview: ;;; ;;; This example implements a simple elevator controller which plans the ;;; movements and actions of all the elevators in a building. It also ;;; acts as a simulator to simulate the behavior of the actual elevators ;;; with some randomness. ;;; ;;; There are three blackboards: ;;; ;;; - The BUILDING-INFO blackboard is used by both the planner and the ;;; simulator. It contains all the information about the building and ;;; its elevators: how many elevators, how they are grouped into banks ;;; of elevators, what floors they serve, etc. ;;; ;;; - The SIMULATOR blackboard contains all the information used by the ;;; simulator. Only simulator KSs should look at these to maintain a ;;; clean separation between the planner and the simulator. ;;; ;;; - The PLANNER blackboard contains all the information used by the ;;; planner. In particular, the space TASKS stores all the tasks that ;;; have been planned for all the elevators. ;;; ;;; The system starts by reading the initial building configuration from ;;; a file and also reading some initial `orders' (i.e., what real world ;;; actions the simulator should simulate). The planner KSs are ;;; triggered in response to the simulated button presses, door ;;; openings, etc., and decide where to send the elevators next. These ;;; planned activities are then noted by the simulator as requests to be ;;; acted upon. This is handled by the SIMULATOR-INTERFACE and ;;; COMPLETE-ACTION KSs. ;;; ;;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ;;; ;;; 03-25-92 File created. ;;; 09-07-93 Updated to GBB V3.0. (Cork) ;;; 01-25-96 Updated to GBB V3.1. (Cork) ;;; 05-06-97 Converted to GBB V3.2. (Cork) ;;; 09-18-98 Converted to GBB V4.0. (Cork) ;;; ;;; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * (in-package "ELEVATOR-EXAMPLE") ;;; ------------------------------------------------------------------------ ;;; Module Definition ;;; ------------------------------------------------------------------------ ;; ;; (define-kti-module :elevator-example ;; (:modules :2d-point-mixin ;; :time-interval-mixin ;; :agenda-shell ;; :gbb-graphics ;; :control-shell-graphics) ;; (:package :elevator-example ;; (:nicknames "ELEVATOR") ;; (:use #.*lisp-package-name* "CLOS" "KTI-TOOLS" "GBB" ;; "AGENDA-SHELL" "CHALKBOX" "GBB-GRAPHICS")) ;; (:directory gbb-root-open "examples") ;; (:files "elevator-example" ;; "elevator-example-graphics") ;; (:auxiliary-files "elevator-building-info" ;; "elevator-orders")) ;;; ------------------------------------------------------------------------ ;;; Variables ;;; ------------------------------------------------------------------------ ;; This controls whether executed actions "tasks" are retained on the ;; blackboard: (defvar *retain-simulation-actions* nil) (defparameter *building-info-file* "elevator-building-info") (defparameter *orders-file* "elevator-orders") (eval-when (compile eval load) (defparameter *max-time* 3600) ;; Length of the simulation in seconds (defparameter *last-order-time* 3000) ;; Last possible time to generate a random order (defparameter *bottom-floor* -5) ;; Lowest floor (defparameter *top-floor* 50) ;; Highest floor (defparameter *max-weight* 1200) ;; Maximum capacity of an elevator cab ) ;; End of eval-when ;; The simulation clock (defvar *world-clock* nil) ;; Number of clock ticks between pauses. (defvar *wait-every-interval* nil) (defparameter *setup-windows-p* t) (defparameter *random-p* t) ;; Total time spent waiting for an elevator and total number ;; of orders that have boarded an elevator. (defvar *total-wait-time* 0) (defvar *total-wait-count* 0) ;;; ------------------------------------------------------------------------ ;;; Top Level Function: ELEVATOR-EXAMPLE ;;; ------------------------------------------------------------------------ (defun elevator-example-startup () (with-graphics-operations-deferred () (delete-blackboard-database) (setf *world-clock* nil) (setf *total-wait-time* 0) (setf *total-wait-count* 0) (if *random-p* (enable-ks 'generate-random-orders) (disable-ks 'generate-random-orders)) (instantiate-blackboard-database 'simulator 'building-info 'planner)) (format *event-print-stream* "~2&;;; Running the elevator controller example....~%")) (pushnew 'elevator-example-startup control-shells::*control-shell-initialization-hooks*) ;;; ------------------------------------------------------------------------ (defun elevator-example-entry () (with-graphics-operations-deferred () ;; Function startup-graphics is defined in the file ;; elevator-example-graphics which may not have been loaded. (when (and *setup-windows-p* (fboundp 'startup-graphics)) (startup-graphics)))) ;; Add to control shell entry hooks: (pushnew 'elevator-example-entry control-shells::*control-shell-entry-hooks*) ;;; ------------------------------------------------------------------------ (defun ELEVATOR-EXAMPLE (&optional *setup-windows-p* &key ((:random *random-p*) t) ((:wait-every *wait-every-interval*) *wait-every-interval*) ((:event-print-stream *event-print-stream*) *standard-output*) ((:orders *orders-file*) *orders-file*)) (start-control-shell :event-print-stream *standard-output*)) ;;; ------------------------------------------------------------------------ (defun elevator-example-exit () (format *event-print-stream* "~2&;;; Leaving the elevator controller example....~%")) (pushnew 'elevator-example-exit control-shells::*control-shell-exit-hooks*) ;;; ------------------------------------------------------------------------ (defun RESUME-ELEVATOR-EXAMPLE (&optional setup-windows &key ((:wait-every *wait-every-interval*) *wait-every-interval*) ((:event-print-stream *event-print-stream*) *standard-output*)) ;; Function startup-graphics is defined in the file ;; elevator-example-graphics which may not have been loaded. (when (and setup-windows (fboundp 'startup-graphics)) (startup-graphics)) (format *event-print-stream* "~2&;;; Resuming the elevator controller example....~%") (resume-control-shell) (format *event-print-stream* "~2&;;; Leaving the elevator controller example....~%")) ;;; ------------------------------------------------------------------------ ;;; Space definitions ;;; ------------------------------------------------------------------------ ;; This blackboard contains all the information about the building and ;; its elevators: how many elevators, how they are grouped into banks of ;; elevators, what floors they serve, etc. (define-space building-info () () (:unit-classes nil)) (define-space elevator-specs (building-info) ((elevator-type :enumerated (:passenger :freight)) (bank-spec :enumerated :any :reset-display-axis t) (x :ordered (0 100)) (y :ordered (0 100)) (floors :ordered (#.*bottom-floor* #.*top-floor*))) (:unit-classes elevator-spec)) (define-space bank-specs (building-info) ((floors :ordered (#.*bottom-floor* #.*top-floor*))) (:unit-classes bank-spec)) (define-space call-button-specs (building-info) ((bank-spec :enumerated :any :reset-display-axis t) (floor :ordered (#.*bottom-floor* #.*top-floor*)) (lit? :enumerated (t nil))) (:unit-classes call-button-spec)) (define-space floor-button-specs (building-info) ((elevator-spec :enumerated :any :reset-display-axis t) (floor :ordered (#.*bottom-floor* #.*top-floor*)) (lit? :enumerated (t nil))) (:unit-classes floor-button-spec)) ;; This blackboard contains all the information used by the simulator. ;; Only simulator KSs should look at these to maintain a clean ;; separation between the planner and the simulator. (define-space simulator () () (:unit-classes nil)) (define-space cabs (simulator) ((elevator-spec :enumerated :any :reset-display-axis t) (floor :ordered (#.*bottom-floor* #.*top-floor*)) (weight :ordered (0 #.*max-weight*)) (status :enumerated (:stopped :doors-open :out-of-service :moving-up :moving-down)) (x :ordered (0 100)) (y :ordered (0 100))) (:unit-classes cab)) (define-space sim-actions (simulator) ((time-interval :ordered (-1 #.*max-time*)) (cab-spec :enumerated :any) (elevator-spec :enumerated :any) (floors :ordered (#.*bottom-floor* #.*top-floor*)) (executed? :enumerated (t nil))) (:unit-classes sim-action)) (define-space orders (simulator) ((time :ordered (0 #.*max-time*)) (start-floor :ordered (#.*bottom-floor* #.*top-floor*)) (up/down :enumerated (:up :down)) (answered :enumerated (t nil)) (bank-spec :enumerated :any)) (:unit-classes order)) (define-space clock (simulator) ((time :ordered (-1 #.*max-time*))) (:unit-classes world-clock)) ;; This blackboard contains all the information used by the planner. ;; In particular, the space TASKS stores all the tasks that have been ;; planned for all the elevators. (define-space planner () () (:unit-classes nil)) (define-space TASKS (planner) ((time-interval :ordered (-1 #.*max-time*)) (bank-spec :enumerated :any) (elevator-spec :enumerated :any) (floors :ordered (#.*bottom-floor* #.*top-floor*)) (task-type :enumerated :any :test equal)) (:unit-classes TASK)) ;;; ------------------------------------------------------------------------ ;;; Dimension-Value Computation Definitions ;;; ------------------------------------------------------------------------ (define-dimension-value-computation elevator-type-dvc ((elevator-type :label)) (:scalar-type symbol)) (define-dimension-value-computation elevator-spec-dvc ((elevator-spec :label)) (:scalar-type (or null elevator-spec))) (define-dimension-value-computation elevator-location-dvc ((x :point elevator-location.x) (y :point elevator-location.y)) (:scalar-type (or null elevator-spec))) (define-dimension-value-computation elevator-floors-dvc ((floors :point)) (:dimension-value-computation-type :set) (:element-type number)) (define-dimension-value-computation bank-spec-dvc ((bank-spec :label)) (:scalar-type (or null bank-spec))) (define-dimension-value-computation bank-spec-from-elevator-spec-dvc ((bank-spec :label (lambda (u) (and u (elevator-spec.bank-spec u))))) (:scalar-type (or null elevator-spec))) ;; Floor ranges are represented as a two-element list: ;; ( ) ;; We could use defstruct here, but there isn't much advantage. (defun make-floor-range (start end) (list start end)) (defun start-floor (floor-range) (first floor-range)) (defun end-floor (floor-range) (second floor-range)) (defun (setf start-floor) (nv floor-range) (setf (first floor-range) nv)) (defun (setf end-floor) (nv floor-range) (setf (second floor-range) nv)) (defmethod min-floor (floor-range) (min (start-floor floor-range) (end-floor floor-range))) (defmethod max-floor (floor-range) (max (start-floor floor-range) (end-floor floor-range))) (define-dimension-value-computation floor-range-dvc ((floors :range (:min min-floor) (:max max-floor))) (:scalar-type list)) (define-dimension-value-computation order-call-button-dvc ((start-floor :point button-spec.floor) (bank-spec :label call-button-spec.bank-spec) (up/down :label call-button-spec.direction)) (:scalar-type (or null call-button-spec))) (define-dimension-value-computation task-type-dvc ((task-type :label get-task-type-dvc))) (defun get-task-type-dvc (arg) (declare (ignore arg)) (typecase *self* (wait-task "Wait") (move-up-task "Move Up") (move-down-task "Move Down") (door-cycle-task "Open Door") (door-cycle-wait-sim-action "Open Door & Wait!") (door-cycle-down-sim-action "Open Door & Down!") (door-cycle-up-sim-action "Open Door & Up!") (move-up-sim-action "Move Up!") (move-down-sim-action "Move Down!") (t (string (type-of *self*))))) ;;; ------------------------------------------------------------------------ ;;; Planner Unit Class Definitions ;;; ------------------------------------------------------------------------ ;; ELEVATOR-SPEC is the static information about each elevator. (define-unit-class elevator-spec (2d-point-mixin) ((2d-point ;; Birds eye location of the elevator in the building. ;; This slot is inherited from 2d-point-mixin. It is ;; repeated here to add another initarg and accessor. :initarg :location :accessor elevator-spec.location) (tasks ;; List of tasks that have been planned for this elevator, ;; collectively called the `plan'. This list is not in order, ;; use the functions first-plan-task, task.next-task, etc. to ;; traverse the plan. :link (task elevator-spec :singular)) (sim-actions ;; Link to the simulator actions in progress for this elevator. :link (sim-action elevator-spec :singular)) (bank-spec ;; Link to the bank that this elevator is a member of. :link (bank-spec elevator-specs) :singular t :link-event-functions (new-elevator-bank-spec) :unlink-event-functions (new-elevator-bank-spec) :link-initialization-event-functions (new-elevator-bank-spec)) (floor-button-specs ;; List of floor buttons in this elevator. :link (floor-button-spec elevator-spec :singular)) (cab ;; Link to the cab (the simulator object) for this elevator. :link (cab elevator-spec :singular) :singular t) (capacity ;; Capacity of this elevator in pounds. :initform 800) (type ;; One of :passenger or :freight. :initform :passenger :type elevator-type-dvc) (floors ;; List of floors that this elevator services. :initform nil) (start/stop-penalty ;; Number of seconds required to start moving :initform 5) (speed ;; Speed of the elevator in floors/second :initform 2) (door-cycle-time ;; Minimum time to open doors, wait a moment, and close them :initform 10)) (:dimensions (bank-spec bank-spec :value-type :label) (elevator-type type) (floors floors :type elevator-floors-dvc)) (:paths '(building-info elevator-specs)) (:generate-initargs tasks sim-actions bank-spec floor-button-specs capacity type start/stop-penalty speed door-cycle-time) (:generate-accessors tasks sim-actions bank-spec floor-button-specs cab capacity type floors start/stop-penalty speed door-cycle-time)) (defmethod print-object-for-display ((elevator elevator-spec) stream) (format stream "#" (unit-name elevator))) ;; The TASK unit uses the value of the BANK-SPEC link in the ;; ELEVATOR-SPEC unit as dimension of its own. Because it gets the ;; value indirectly through its own ELEVATOR-SPEC link, we must ;; explicitly notify GBB that the bank spec for an elevator has changed ;; so that the dimensional values will be updated accordingly. Note, ;; that the BANK-SPEC dimension in the TASK class is cached so that it ;; can be repositioned correctly. ;; ;; Also each elevator gets the value of its FLOORS slot from the ;; BANK-SPEC. So when the BANK-SPEC is initialized or changes we must ;; update the FLOORS slot. (defun new-elevator-bank-spec (event-name &key unit current-value &allow-other-keys) ;; UNIT is the elvator-spec that has been modified. ;; CURRENT-VALUE is the new value of the bank-spec link. (declare (ignore event-name)) (dolist (task (elevator-spec.tasks unit)) (reposition-unit task :bank-spec)) (setf (elevator-spec.floors unit) (bank-spec.floors current-value))) ;;; ------------------------------------------------------------------------ ;; BANK-SPEC contains the static information about each elevator ;; bank. An elevator bank is a group of elevators all serving the ;; same floors. (define-unit-class bank-spec () ((floors :type elevator-floors-dvc) (elevator-specs :link (elevator-spec bank-spec :singular)) (call-button-specs :link (call-button-spec bank-spec :singular))) (:dimensions (floors floors)) (:paths '(building-info bank-specs)) (:generate-initargs t) (:generate-accessors t)) (defmethod print-object-for-display ((bank bank-spec) stream) (format stream "#<~a>" (unit-name bank))) ;;; ------------------------------------------------------------------------ (define-unit-class basic-button-spec () ((floor :initform nil) (lit? :initform nil)) (:dimensions (floor floor :value-type :point) (lit? lit? :value-type :label)) (:generate-initargs floor) (:generate-accessors) (:accessor-prefix "BUTTON-SPEC.")) ;;; ------------------------------------------------------------------------ (define-unit-class floor-button-spec (basic-button-spec) ((elevator-spec :link (elevator-spec floor-button-specs) :singular t)) (:dimensions (elevator-spec elevator-spec :value-type elevator-spec-dvc)) (:paths '(building-info floor-button-specs)) (:generate-initargs) (:generate-accessors)) (defmethod print-object-for-display ((button floor-button-spec) stream) (format stream "#