next up previous contents index
Next: Coupled DEVS Examples Up: Atomic DEVS Examples Previous: Timer   Contents   Index


Vending Machine

Consider a simple vending machine (VM) from which we can get Pepsi and Coke. Figure 3.2 illustrates the state transition diagram of VM we are considering.

Figure 3.2: State Transition Diagram of Vending Machine
\begin{figure}\centering\mbox {\epsfig{file=VM2,width=0.8\columnwidth}}\end{figure}

There are three input events such as ?dollar for ``input a dollar'', ?pepsi_btn for ``push the Pepsi button'', ?coke_btn for ``push the Coke button''. Similarly, we can model three output events such as !dollar for ``a dollar out (because of timeout of menu selection)'', !pepsi for ``Pepsi out'' and !coke for ``Coke out'. 4.2 The state of VM can be either Idle for ``Idle'', Wait for ``Wait''(that is waiting for selection of Pesi or Coke), O_Pepsi for ``output Pepsi'' and O_Coke for ``output Coke''. And their life times are: 15 time units for Wait, 2 time unites for both O_Pepsi and O_Coke, $ \infty$ for Idle which is denoted by inf in Figure 3.2. 4.3

At the beginning (t=0), VM is at Idle. If we put ?dollar in, it changes the state into Wait simultaneously updating $ t_s=15$ and $ t_e = 0$ for the state. While in the state, if VM receives ?pepsi_btn (resp. ?coke_btn), it enters into the state O_Pepsi (resp. O_Coke) and simultaneously updates $ t_s=2$ and $ t_e = 0$ . While in the state O_Pepsi or O_Coke, VM ignores any input and preserves the state. Similarly, while in the state Wait, VM ignores ?dollar input.

After staying at Wait for 15 time unites, VM returns to Idle state and outputs the dollar if we don't select Pepi or Coke within the 15 time units. However, if we had selected one of them, VM changes its state into O_Pepsi (resp. O_Coke). Then after 2 time unites, VM outputs !pepsi (resp. !coke) and returns to Idle.

The example of Ex_VendingMachine shows an atomic DEVS model of VM which is defined in VendingMachine.cs file. The class VM has three input port idollar, pepsi_btn and coke_btn; three output port odollar, pepsi, coke, all assigned by returning values of the AddIP and AddOP functions in the constructor.

    public class VM : Atomic
    {
        public InputPort idollar, pepsi_btn, coke_btn;
        public OutputPort odollar, pepsi, coke;
        enum PHASE { Idle, Wait, O_Pepsi, O_Coke }
        PHASE m_phase;

        public VM(string name) : base(name, TimeUnit.Sec)
        {
            idollar = AddIP("dollar");
            pepsi_btn = AddIP("pepsi_btn");
            coke_btn = AddIP("coke_btn");

            odollar = AddOP("dollar");
            pepsi = AddOP("pepsi");
            coke = AddOP("coke");
            init();
        }

VM's initial state is set to Idle in init(). The lifespan of each state is defined in tau() as 15, 2, 2, and $ \infty$ for Wait, O_Pepsi, O_Coke, and Idle, respectively.

    public override void init() { m_phase = PHASE.Idle; }
    public override double tau()
    {
        if (m_phase == PHASE.Wait)
            return 15;
        else if (m_phase == PHASE.O_Pepsi)
            return 2;
        else if (m_phase == PHASE.O_Coke)
            return 2;
        else
            return double.MaxValue;
    }

The input transition function delta_x defines every arc triggered by an input event in Figure 3.2 and returns true for each such arc. If the input event idollar arrives while VM is not in state Idle, or if the input events pepsi_btn or coke_btn arrive while VM is not in state Wait, delta_x returns false, and the input is ignored.

    public override bool delta_x(PortValue x)
    {
        if (m_phase == PHASE.Idle && x.port == idollar)
        {
            m_phase = PHASE.Wait;
            return true; // Reschedule Me
        }
        else if (m_phase == PHASE.Wait && x.port == pepsi_btn)
        {
            m_phase = PHASE.O_Pepsi;
            return true; // Reschedule Me
        }
        else if (m_phase == PHASE.Wait && x.port == coke_btn)
        {
            m_phase = PHASE.O_Coke;
            return true; // Reschedule Me
        }
        return false; // Ignore the input
    }
The output transition function delta_y defines every arc generating an output event in Figure 3.2.
    public override void delta_y(ref PortValue ys)
    {
        if (m_phase == PHASE.Wait)
            ys.Set(odollar);
        else if (m_phase == PHASE.O_Pepsi)
            ys.Set(pepsi);
        else if (m_phase == PHASE.O_Coke)
            ys.Set(coke);
        m_phase = PHASE.Idle;
    }
The virtual function Get_s() is also overridden and returns an m_phase.ToString().
    public override string Get_s()
    {
        return m_phase.ToString();
    }
 }

Since this vending machine example needs the user input during a simulation run, we need to define a callback function for the user input. In Program_VM.cs file, we can see the following static function.

    static PortValue InjectMsg(Devs model)
    {
        if (model is VM)
        {
            VM vm = (VM)model;
            Console.Write("[d]ollar [p]epsi_botton [c]oca_botton > ");
            string input = Console.ReadLine();
            if (input == "d")
                return new PortValue(vm.idollar);
            else if (input == "p")
                return new PortValue(vm.pepsi_btn);
            else if (input == "c")
                return new PortValue(vm.coke_btn);
            else
            {
                Console.WriteLine("Invalid input! Try again!");
                return new PortValue(null,null);
            }
        }
        else
            throw new Exception("Invalid Model!");
    }
The callback function InjectMsg casts the type of md from Devs to VM. And the user-input of either d, p, or c is mapped to PortValue(vm.idollar), PortValue(vm.pepsi_btn), or PortValue(vm.coke_btn), respectively.

The last part the the code in Program_VM.cs runs the simulation engine. First we make vm as an instance of VM, and plug vm into an instance of SRTEngine with the simulation ending time=10000 using the above callback function.

    static void Main(string[] args)
    {
        VM vm = new VM("VM");
        SRTEngine Engine = new SRTEngine(vm, 10000, InjectMsg);
        Engine.RunConsoleMenu();
    }

Let's try the command step. Observe that since the initial state $ s_0$ of VM is Idle and its lifespan tau(Idle)=$ \infty$ , and the initial schedule is also t_s=$ \infty$ . In this case, the elapsed time t_e cannot ever reach t_s. Thus this command step doesn't stop until $ t_e$ becomes 1000 which is the simulation ending time (unless the user interrupts the simulation).

In this case, we can stop the simulation run using pause or p command, followed by Enter key. The following screen shows the situation if we make it pause at 8.859.

(VM:Idle, t_s=inf, t_e=8.859) at 8.859

Let's try inject or i. Then we can see the console output which is produced by the above InjectMsg(Devs md) as follows.

[d]ollar [p]epsi_botton [c]oca_botton >
If we input d, we can see the input causes the state to transition from Idle to Wait as follows.
(VM:Idle, t_s=inf, t_e=8.859)
 --({?dollar,?VM.dollar}, t_c=8.859)-->
(VM:Wait, t_s=15.000, t_e=0.000)

Now, we use continue or c to resume stepping again. If we want to pause again and inject a menu selection such as pepsi_btn or coke_btn, we can do that just like before.

Exercise 4.2   Consider modifying the VM model in EX_VendingMachine in order to add the behavior of rejecting a second dollar input when VM is the state Wait. To model this, let's add a state Reject whose lifespan is 0. We define the output transition $ \delta_y$ at Reject as delta_y(Reject) = (!dollar, Wait). However there are two ways of rescheduling of t_s and t_e of the the state Wait when VM comes back to the state. Let's try each of the following two ways.
  1. Reset t_s=15 and t_e=0.
  2. Return t_s and t_e to the values they had right before the input of the additional dollar.


next up previous contents index
Next: Coupled DEVS Examples Up: Atomic DEVS Examples Previous: Timer   Contents   Index
MHHwang 2007-05-08