Anatomy of a Submodule

Before integrating a subsystem into a machine using Piper, build the subsystem in isolation.

Now let’s take a look at a Piper Submodules.

You can think of Piper as a plugin system. You define your subsystem and Piper coordinates it with the rest system. As an API with a well defined interface, we have the ability to give you some good templates to start with.

Here is your cyclic task. Just add the PiperModule interface to the top of whatever task you are integrating.

// MyTaskName.ST

PROGRAM _CYCLIC

	// Call PiperModuleInterface to interface with the rest of the machine
	PiperModuleInterface;
	
	//Put your code here

END_PROGRAM

Here is a local var file to define the variables required by Piper

// PiperModule.var

VAR
	ModuleInterface : Module_Interface_typ; //Structure for the user to interface to
	Module : Piper_Module_Fub;              //Function block for processing commands
END_VAR

This is the main interface for integrating into Piper.

//PiperInterface.st

ACTION PiperModuleInterface: 
	
	//Give this module a name so it is easier to debug
	ModuleInterface.ModuleName:=	'Name';

	//Add a module to the Piper
	Module.ModuleInterface:=	ADR(ModuleInterface);
	Module.Piper:= 	ADR(gMachine);
	Module();

	// Handle any machine states that this module needs to respond to
	CASE ModuleInterface.PiperState OF
//		MACH_ST_BOOTING:		
//		MACH_ST_CLEARING:
//		MACH_ST_STOPPED:
//		MACH_ST_STARTING:
//		MACH_ST_IDLE:
//		MACH_ST_SUSPENDED:
//		MACH_ST_EXECUTE:
//		MACH_ST_STOPPING:
//		MACH_ST_ABORTING:
//		MACH_ST_ABORTED:
//		MACH_ST_HOLDING:
//		MACH_ST_HELD:
//		MACH_ST_UNHOLDING:
//		MACH_ST_SUSPENDING:
//		MACH_ST_UNSUSPENDING:
//		MACH_ST_RESETTING:
//		MACH_ST_COMPLETING:
//		MACH_ST_COMPLETE:
		
		MACH_ST_BYPASSED:
		
		ELSE
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;
		
	END_CASE	
	
END_ACTION

Since we built our task in isolation, we can easily integrate into the machine. We start by uncommenting any states that we need to handle and responding. From here I will remove some reduntant code.. so beware.

It’s a good idea to uncomment the states you need to respond to, and respond. Now you can test that your system is integrated by setting gMachine.IN.CMD.xxx and you should see your system going through states.

//PiperInterface.st

ACTION PiperModuleInterface: 
	
    ...

	// Handle any machine states that this module needs to respond to
	CASE ModuleInterface.PiperState OF
		MACH_ST_BOOTING:		
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;

		MACH_ST_ABORTING:
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;

		MACH_ST_RESETTING:
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;

//		MACH_ST_CLEARING:
//		MACH_ST_STOPPED:
//		MACH_ST_STARTING:
...
		MACH_ST_BYPASSED:
		
		ELSE
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;
		
	END_CASE	
	
END_ACTION

Now let’s make it useful by integrating our subsystem. We will force the system to wait for us to be initialized before booting. We should also power on during Resetting and power off during Aborting.

//PiperInterface.st

ACTION PiperModuleInterface: 
	
    ...

	// Handle any machine states that this module needs to respond to
	CASE ModuleInterface.PiperState OF
		MACH_ST_BOOTING:		

            //If I'm done initializing, let the system know
            IF gThisSubsystem.status.isBootedUp THEN
    
                //Responding with the current state indicates we are finished with the state
    			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;
    
            END_IF
	
    	MACH_ST_ABORTING:

            gThisSubsystem.cmd.PowerOn = FALSE;

            //Respond immediatley. We are sure power off will work
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;

		MACH_ST_RESETTING:

            //It's ok to hold the power on true since our system was designed to detect edges
            gThisSubsystem.cmd.PowerOn = TRUE;
    
            //Once we have successully powered on we respond with done
            IF gThisSubsystem.status.isPoweredOn THEN
    
    			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;

            //If we got an error, respond so and it will force the system into aborting. 
            // You may also interface to gMachine.IN.CMD.Abort, but this is not traceable.
            ELSIF gThisSubsystem.status.Error THEN
    
    			ModuleInterface.ModuleResponse:=	MACH_ST_ERROR;

            END_IF


//		MACH_ST_CLEARING:
//		MACH_ST_STOPPED:
//		MACH_ST_STARTING:
...
		MACH_ST_BYPASSED:
		
		ELSE
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;
		
	END_CASE	
	
END_ACTION

Group statuses

A common practice is to check if an array of items is ready before moving on. This usually works by settings a groupStatus to true, then setting to false if anything is not complete. If you make it to the end and your group status is still true then you are GOOD TO GO!

//PiperInterface.st

ACTION PiperModuleInterface: 
	
    ...

	// Handle any machine states that this module needs to respond to
	CASE ModuleInterface.PiperState OF

		MACH_ST_RESETTING:

			//Set the status to true and assume we are done
			groupStatus := TRUE;

			//Check all the axes, if any are NOT done, we need to wait
			FOR axis := 0 TO AXES_MAI DO

				gAxis[axis].Power := TRUE;

				IF NOT gAxis[axis].isPowered THEN

					groupStatus := FALSE;

				END_IF

			END_FOR

			//If groups status is still true then it means every axis is ready
			//	To move on
			IF groupStatus = TRUE THEN

				ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;

			END_IF

...
		MACH_ST_BYPASSED:
		
		ELSE
			ModuleInterface.ModuleResponse:=	ModuleInterface.PiperState;
		
	END_CASE	
	
END_ACTION

That’s IT

You’ve integrated into Piper.

There is still more to learn. As long as you followed the rules about State Machines your module should function well. In case you don’t remember:

  1. Expect to enter any State at any time.
  2. Do not call Function blocks within a state
  3. Do not implement Sequences within State Unless you are using Piper Substates.