pState Examples
from pstate import *
from IPython.display import display
display(pChart("water_monitoring"))
State Invariants
PRISM uses properties for analysis of its models. In pState, we use state invariants, which are simpler to read and write, and serve the same purpose. Below are some of these properties translated into state invariants.
# display(pChart(""))
Luxury Car Seat
Some cars contain luxury features, one of which is power seats. Power seats have a panel of controls that allow you to adjust your seating position automatically. The seats are controlled by motors, and the one in the example below has 5 motors. These are:
- Rear Height Motor (RH)
- Can move up, down
- Longitudinal Adjustment Motor (LA)
- Can move forward, backward
- Front Height Motor (FH)
- Can move up, down
- Backrest Motor (B)
- Can move forward, backward
- Head Restraint Motor (HR)
- Can move up, down
Each motor has 2 corresponding buttons (one for each direction). There are also 3 additional buttons, M, M1, M2, which control the memory functionality. If you hold M and press and release M1 or M2, the current position of the seat will be saved into their respective memory slots and can be loaded at a later time. To load the seat settings you press and M1 or M2 to load the seat position from memory.
There are two groups of motors, Group 1 (LA, RH) and Group 2 (FH, B, HR). Only one motor per group can be active at any given time. Within each group, there are motors which take priority over another, meaning if they are both being pressed, the one with the higher priority will be active. In Group 1, LA has priority over RH. In Group 2, FH has priority over B, B has priority over HR.
Each motor is also equipped with a Hall effect sensor which allow them to keep track of their position. The motors are controlled by ticks.
The number of ticks required for movement from one stop to the other is:
- RH: 250 ticks
- LA: 600 ticks
- FH: 250 ticks
- B: 1100 ticks
- HR: 130 ticks
display(pChart("car_seat"))
Landing Gear
The landing gear of planes are usually very simple, however they are safety critical systems. In the example below, the landing gear of a typical airplane is modelled, along with the landing gear level in the cockpit. One important aspect is the state invariant of GearSystem, which says that if the landing gear is extending or retracting, the doors for the gear should not be opening, closing or closed.
display(pChart("landing_gear"))
Production Cell
The example below is of a production cell which consists of 5 interacting machines:
- Feed Belt
- Table
- 2-Armed Robot
- Press
- Deposit Belt
The production cell presses metal plates which arrive on the feed belt, and delivers them to the deposit belt. The process is as follows:
- The feed belt delivers the plate to the table
- The table elevates and rotates so the robot can grip it with arm 1
- The robot grips the plate with arm 1, rotates CCW and feeds the press
- The press forms the plate
- The robot rotates CW and arm 2 grips the pressed plate
- The robot rotates CCW and drops the plate on the deposit belt
- The deposit belt delivers the plate to the end
The machine processes several plates at a time.
Feed Belt
The feed belt transports plates from one side to the other. It can detect when a plate arrives at the end and when it falls onto the table. The belt can be switched on and off. It is on when waiting for a plate, and off when a plate is at the end but cannot be delivered.
The belt is initially running and empty.
display(pChart("feed_belt"))
Table
The table lifts and rotates in order to position the plate for the robot to pick it up. It consists of two motors, one for elevating and one for rotating. The table can sense when it is at its upper/lower end and left/right end.
Initially, the table is in the lower-left position.
display(pChart("table"))
Robot
The robot consists of 2 orthogonal arms, which can extend and retract, and a rotating base. There are 3 relevant positions for the base:
- Position 1: aligns arm 1 with the table
- Position 2: aligns arm 2 with the press
- Position 3: aligns arm 1 with the press and arm 2 with the delivery belt
The arms have 3 sensor positions inner, middle, and outer. Each arm also has an electromagnetic gripper which can be turned on and off. The arms have to be in these positions during the process:
- In position 1, arm 1 has to extend to its middle position
- This lets arm 1 grab the plate from the table
- In position 2, arm 2 has to extend to its outer position
- This lets arm 2 pick up the plate from the press
- In position 3, arm 1 has to extend to its outer position and arm 2 has to extend to its middle position
- This is lets arm 1 load the plate into the press and lets arm 2 drop the plate on the delivery belt
- When the base is rotating, the arms of the robot must be fully retracted and in the inner position.
The initial position of the robot base is in position 3, arm 1 retracted to its inner position, arm 2 extended to its middle position, and both grippers are off.
Robot Base
display(pChart("robot_base"))
Robot Arms
display(pChart("robot_arms"))
display(pChart("robot"))
Press
The press consists of a platform which moves up and down, forming the plate against a mould. It closes by moving the platform up, and closes by moving it down.
Arm 1 and arm 2 are at different heights, so the press has to be at different positions for loading and unloading. There is a lower, middle, and upper position. It is unloaded by arm 2 in the lower position, and loaded by arm 1 in the middle position.
For safety, the following conditions are met:
- When the robot is in position 3, arm 1 only extends if the platform is in the middle position.
- When the robot is in position 2, arm 2 only extends if the platform is in the lower position.
- The platform only moves when after the robot loading/unloading retracts its arm to the inner position.
The initial position of the press is in its lower position, and is empty.
display(pChart("press"))
Deposit Belt
The deposit belt transports plates from one end to the other. The belt senses when a plate arrives at the end and when it’s been removed. The belt can be switched on or off. It is off when waiting for a plate to be placed or when a plate reaches the end. It is on when a plate is placed on it and there is no plate on the end. A new plate can only be placed on the belt if there is no additional plate on, or at the end of, the belt.
display(pChart("deposit_belt"))
KeyFob
display(pChart("key_fob"))
Sender Reciever
display(pChart("sender_reciever"))
GPS Module
display(pChart("gps_module"))
Examples using Widgets
Safe
import ipywidgets as widgets
safe_chart = pChart("safe")
display(safe_chart)
number1 = widgets.BoundedIntText(value=0, min=0, max=60, step=1, description='1st Number:', disabled=False)
number2 = widgets.BoundedIntText(value=0, min=0, max=60, step=1, description='2nd Number:', disabled=True)
number3 = widgets.BoundedIntText(value=0, min=0, max=60, step=1, description='3rd Number:', disabled=True)
reset_button = widgets.Button(description='Reset', button_style='danger')
reset = False
def reset_clicked(button):
global reset
reset = True
reset_state(button)
def reset_state(button):
global number1, number2, number3, reset
reset_clicked()
number1.value = 0
number2.value = 0
number3.value = 0
number1.disabled = False
number2.disabled = True
number3.disabled = True
reset = False
def cracking(val1, val2, val3):
global number1, number2, number3
result1 = val1 == 0b100001
if result1:
print("Click")
number1.disabled = True
number2.disabled = False
else:
print("The safe is locked.")
result2 = result1 and val2 == 0b101100
if result2:
print("Clack")
number2.disabled = True
number3.disabled = False
result3 = result2 and val3 == 0b110111
if result3:
print("You've cracked the safe!")
number3.disabled = True
out = widgets.interactive_output(cracking, {'val1': number1, 'val2': number2, 'val3': number3})
reset_button.on_click(reset_state)
left_VBox = widgets.VBox([number1, number2, number3])
right_VBox = widgets.VBox([reset_button, out])
display(widgets.HBox([left_VBox, right_VBox]))
Light switch
light_switch_chart = pChart("light_switch")
display(light_switch_chart)
# Lightbulb SVG
# Obtained from: https://www.svgrepo.com/svg/17720/lightbulb-on
def lightbulb_svg(light):
return widgets.HTML('''
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="300px" height="300px" viewBox="0 0 468.759 468.759">
<g fill="{0}">
<path d="M234.38,0c-72.257,0-131.039,59.728-131.039,133.146c0,29.205,16.976,55.49,31.951,78.672
c8.831,13.66,17.165,26.563,19.036,36.209c5.261,27.225,8.275,91.806,8.299,92.456c0.212,4.516,3.921,8.068,8.449,8.068h126.611
c2.329,0,4.552-0.952,6.147-2.642c1.596-1.697,2.424-3.967,2.293-6.283c-1.572-28.307-0.484-78.489,9.127-92.699
c1.666-2.459,3.582-5.178,5.65-8.156c16.656-23.759,44.514-63.541,44.514-105.632C365.406,59.734,306.636,0,234.38,0z
M307.062,229.092c-2.146,3.047-4.091,5.85-5.805,8.367c-13.252,19.588-13.074,72.359-12.389,94.193H179.114
c-0.993-18.535-3.794-64.025-8.195-86.822c-2.494-12.874-11.266-26.451-21.42-42.183c-13.716-21.226-29.247-45.273-29.247-69.496
c0-64.096,51.199-116.235,114.122-116.235c62.924,0,114.123,52.139,114.123,116.235
C348.502,169.911,322.554,206.956,307.062,229.092z M168.864,361.118h131.033v65.521c0,12.318-7.979,22.662-19.026,26.445
c-2.052,8.973-10.025,15.675-19.612,15.675h-53.765c-9.587,0-17.566-6.702-19.612-15.675
c-11.038-3.783-19.024-14.127-19.024-26.445v-65.521H168.864z M215.985,41.304c0.919,3.212 0.931,6.567-4.152,7.48
c-59.32,17.002-59.92,80.741-59.92,83.442c0,3.34-2.692,6.059-6.041,6.064h-0.012c-3.333,0-6.041-2.687-6.053-6.023
c0-0.75,0.473-75.566,68.698-95.116C211.694,36.232,215.066,38.083,215.985,41.304z M319.119,163.687
c-1.194,1.327-2.843,1.998-4.492,1.998c-1.442,0-2.902-0.515-4.055-1.555c-2.482-2.24-2.684-6.064-0.438-8.553
c19.15-21.258,3.168-47.242,3.003-47.502c-1.785-2.831-0.935-6.567,1.892-8.346c2.825-1.785,6.561-0.932,8.346,1.894
C330.728,113.3,339.311,141.294,319.119,163.687"/>
</g></svg>'''.format('#FFFF00' if light else '#000000'))
light = False
lightbulb = lightbulb_svg(light)
switch_button = widgets.Button(
description='Flip switch',
button_style='success' if light else 'danger'
)
info_button = widgets.Button(
description = 'Light is on' if light else 'Light is off',
button_style = '',
disabled = True
)
def light_switch(b):
global light, lightbulb
light = False if light else True
switch_button.button_style = 'success' if light else 'danger'
info_button.description = 'Light is on' if light else 'Light is off'
lightbulb.value = lightbulb_svg(light).value
switch_button.on_click(light_switch)
widget = widgets.HBox([switch_button, info_button, lightbulb])
display(widget)
Speedometer
def update_svg(rotation):
return widgets.HTML('''<?xml version='1.0' encoding='iso-8859-1'?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 238 115"
width='460' height='238'
xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 400 400">
<g>
<circle r="100" cx="50%" cy="100%" stroke="#FF0000" stroke-width="15" stroke-dasharray="314.159, 628.318" fill="none" transform="rotate (-180 115 115)" />
<rect width="100" height="4" transform="translate(111, 113) rotate({0}, 0, 2)"/>
</g>
</svg>'''.format(rotation))
def update_speed(speed):
global speedometer_html
speedometer_html.value = update_svg(-180 + speed['new']).value
gas_pedal = widgets.IntSlider(orientation="vertical", min=0, max=180)
speedometer_html = update_svg(-180)
speedometer = widgets.HBox([gas_pedal, speedometer_html])
gas_pedal.observe(update_speed, names=['value'])
display(speedometer)