SinelaboreRT Header Logo

SinelaboreRT

As simple as possible, but not any simpler!

User Tools

Site Tools


wiki:backends:rust_lang

Generating Rust Code

Rust a is a relatively new programming language aiming to guarantee memory-safety and thread-safety. Rust is fast and memory-efficient with no runtime or garbage collector. More information about Rust is available at https://www.rust-lang.org To generate Rust code call the code generator with the following commandline flag −l Rust. The generated code is using basic Rust features like match, if/then/else and can easily be understood.

The output name -o test defines the generated state machine file name (Note the lower case ’t’ to follow Rust convention), the name of the state machine type and the prefix for the event and state enums.

The following image shows a simple microwave oven in the integrated state machine editor.

 Microwave oven example in the integrated state machine editor.

Code generation works as follows:

java -cp "path to installation/bin/*" codegen.Main -l rust -p ssc -o oven oven.xml

The fully generated Rust code from the oven state machine looks as follows:

The fully generated Rust code from the oven state machine looks as follows:

/* Command line options: -Trace -l rust -p ssc -o oven oven.xml   */
/* This file is generated from oven.xml - do not edit manually  */
/* Generated on: Mon Nov 28 17:57:40 CET 2022 / Version 5.5.5.5 */
 
 
use std::fmt;
 
 
/// States in which the state machine can be
#[derive(Debug,PartialEq,Copy,Clone)]
pub enum OvenStates{
	Super,
	Completed,
	Cooking,
	CookingPause,
	Idle,
}
 
impl fmt::Display for OvenStates {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		match *self {
			OvenStates::Super => write!(f, "Super"),
			OvenStates::Completed => write!(f, "Completed"),
			OvenStates::Cooking => write!(f, "Cooking"),
			OvenStates::CookingPause => write!(f, "CookingPause"),
			OvenStates::Idle => write!(f, "Idle"),
		}
	}
}
 
 
/// Events that can be sent to the state machine
#[derive(Debug,PartialEq,Copy,Clone)]
pub enum OvenEvents {
	EvDec,
	EvTimeout,
	EvDoorClosed,
	EvDoorOpen,
	EvPowerLow,
	EvPowerHigh,
	EvInc,
	OvenNoMsg,
}
 
 
impl fmt::Display for OvenEvents {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		match *self {
			OvenEvents::EvDec => write!(f, "EvDec"),
			OvenEvents::EvTimeout => write!(f, "EvTimeout"),
			OvenEvents::EvDoorClosed => write!(f, "EvDoorClosed"),
			OvenEvents::EvDoorOpen => write!(f, "EvDoorOpen"),
			OvenEvents::EvPowerLow => write!(f, "EvPowerLow"),
			OvenEvents::EvPowerHigh => write!(f, "EvPowerHigh"),
			OvenEvents::EvInc => write!(f, "EvInc"),
			OvenEvents::OvenNoMsg => write!(f, "OvenNoMsg"),
		}
	}
}
 
 
#[macro_export]
macro_rules! def_fsm{
	($data_t:ty , $timer_service_t:ty $(,$element:ident: $ty:ty = $ex:expr)*) => {
 
	/* State machine struct
	 * is_init flag indicates whether the machine event handler was called the first time 
	 * state_var is the top level state variable 
	 * StateXXX are the state variables for hierachical or regions 
	 */
	#[derive(Debug,Copy,Clone)]
	pub struct Oven {
		pub is_init: bool,
		pub state_var: OvenStates,
		pub state_varmain_region: OvenStates,
		/* Start of user defined attributes */
		$($element: $ty),*
		/* End of user defined attributes */
	}
 
	impl Default for Oven {
		fn default() -> Self{
			Oven {
				is_init : false,
				state_var : OvenStates::Super, /* Set init for top level state */
				state_varmain_region : OvenStates::Idle, /* set init state of main_region */
 
				/* Start of user defined attributes */
				 $($element: $ex),*
				/* End of user defined attributes */
			}
		}
	}
 
	impl Oven{
		pub fn handle_event(&mut self, ev: OvenEvents,
			data:$data_t, timer_service:$timer_service_t
			) -> usize {
 
 
			let ev_consumed : usize;
 
			// Create copy of statevar
			let mut fsm_clone : Oven = self.clone();
 
			if self.is_init == false {
				fsm_clone.is_init = true;
				fsm_clone.initialize(data, timer_service);
			}
 
			// Action code
			/* just a comment */
 
			ev_consumed = fsm_clone.oven_main_machine(ev, data, timer_service);
 
			// Copy state variables back
			*self = fsm_clone;
 
			return ev_consumed;
		}
 
 
 
 
		/* Region code for state main_region */
 
		fn oven_main_region(&mut self, ev: OvenEvents,
			data:$data_t, timer_service:$timer_service_t) -> usize {
 
			let mut ev_consumed : usize = 0;
 
			match self.state_varmain_region {
 
				OvenStates::Completed => {
					if ev == OvenEvents::EvDoorOpen {
						/* Transition from Completed to Idle */
						ev_consumed=1;
 
						/* OnEntry code of state Idle */
						data.oven_off();
 
						/* adjust state variables  */
						self.state_varmain_region = OvenStates::Idle;
						data.log("Completed".to_string(), "Idle".to_string(), "EvDoorOpen".to_string());
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match Completed  */
 
				OvenStates::Cooking => {
					if ev == OvenEvents::EvDoorOpen {
						/* Transition from Cooking to CookingPause */
						ev_consumed=1;
 
						/* Action code for transition  */
						data.oven_off();
						timer_service.pause(self.timer_id);
 
 
						/* adjust state variables  */
						self.state_varmain_region = OvenStates::CookingPause;
						data.log("Cooking".to_string(), "CookingPause".to_string(), "EvDoorOpen".to_string());
					} else if ev == OvenEvents::EvTimeout {
						/* Transition from Cooking to Completed */
						ev_consumed=1;
 
						/* Action code for transition  */
						data.oven_off();
						timer_service.stop(self.timer_id);
 
						/* OnEntry code of state Completed */
						self.time=0;
 
						/* adjust state variables  */
						self.state_varmain_region = OvenStates::Completed;
						data.log("Cooking".to_string(), "Completed".to_string(), "EvTimeout".to_string());
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match Cooking  */
 
				OvenStates::CookingPause => {
					if ev == OvenEvents::EvDoorClosed {
						/* Transition from CookingPause to Cooking */
						ev_consumed=1;
 
						/* Action code for transition  */
						timer_service.cont(self.timer_id);
 
						/* OnEntry code of state Cooking */
						data.oven_on();
 
						/* adjust state variables  */
						self.state_varmain_region = OvenStates::Cooking;
						data.log("CookingPause".to_string(), "Cooking".to_string(), "EvDoorClosed".to_string());
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match CookingPause  */
 
				OvenStates::Idle => {
					if ev == OvenEvents::EvDoorClosed {
						if self.time > 0 {
							/* Transition from Idle to Cooking */
							ev_consumed=1;
 
							/* Action code for transition  */
							timer_service.start(self.timer_id, self.time);
 
							/* OnEntry code of state Cooking */
							data.oven_on();
 
							/* adjust state variables  */
							self.state_varmain_region = OvenStates::Cooking;
							data.log("Idle".to_string(), "Cooking".to_string(), "EvDoorClosed[self.time > 0]".to_string());
						} else {
							/* Intentionally left blank */
						} /* End of event selection */
					} else {
						/* Intentionally left blank */
					} /* End of event selection */
				} /* end of match Idle  */
 
				_ => {
					/* Intentionally left blank */
				 }
			 } /* End match stateVar_root */
 
			return ev_consumed;
		}
 
 
		pub fn initialize(&mut self, data:$data_t, timer_service:$timer_service_t){
			self.is_init = true;
 
 
			self.timer_id = timer_service.create(TimerType::SingleShot,OvenEvents::EvTimeout);
			data.oven_off();
 
 
		}
 
 
		fn oven_main_machine(&mut self, ev: OvenEvents,
			data:$data_t, timer_service:$timer_service_t) -> usize {
 
 
			let mut ev_consumed : usize = 0;
 
 
			match self.state_var {
 
				OvenStates::Super => {
					/* calling region code */
					ev_consumed |= self.oven_main_region(ev, data, timer_service);
 
					/* Check if event was already processed  */
					if ev_consumed==0{
 
						if ev == OvenEvents::EvDec {
							/* Transition from Super to Super */
 
							/* Exit code for regions in state Super */
 
							/* Action code for transition  */
							if self.time >= 1000{self.time += 1000;}
 
 
 
							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */
 
 
							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvDec".to_string());
						} else if ev == OvenEvents::EvInc {
							/* Transition from Super to Super */
 
							/* Exit code for regions in state Super */
 
							/* Action code for transition  */
							self.time += 1000;
 
 
							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */
 
 
							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvInc".to_string());
						} else if ev == OvenEvents::EvPowerHigh {
							/* Transition from Super to Super */
 
							/* Exit code for regions in state Super */
 
							/* Action code for transition  */
							data.oven_set_pwr(2000);
 
 
							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */
 
 
							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvPowerHigh".to_string());
						} else if ev == OvenEvents::EvPowerLow {
							/* Transition from Super to Super */
 
							/* Exit code for regions in state Super */
 
							/* Action code for transition  */
							data.oven_set_pwr(400);
 
 
							/* Entry code for regions in state Super */
							data.oven_off();
							/* Default in entry chain  */
							self.state_varmain_region = OvenStates::Idle;/* Default in entry chain  */
 
 
							/* adjust state variables  */
							self.state_var = OvenStates::Super;
							data.log("Super".to_string(), "Super".to_string(), "EvPowerLow".to_string());
						} else {
							/* Intentionally left blank */
						} /* End of event selection */
					}
				} /* end of match Super  */
 
				_ => {
					/* Intentionally left blank */
				 }
			 } /* End match stateVar_root */
			return ev_consumed;
		}
 
 
	}
};
}

The generator uses the macro features of Rust. A concrete type of a machine must be defined by using a def_fsm call before the machine can be used.

// define state machine
def_fsm!(&mut UserData, &mut crate::Timers<OvenEvents>, timer_id:usize=99,time:u32=0);
let mut timers = Timers::new();
let mut myfsm : Oven = Default::default();
let mut msg = OvenEvents::EvInc;
myfsm.handle_event(msg, &mut data, &mut timers);
...

The first macro parameter is a user defined type used as a parameter of the state machine handler. It is available inside the event handler with variable name data and type UserData.

The intended purpose is to provide a way for the user to hand over any kind of data to the state machine handler function which can then be used in the state machine. A simple use case is to hand over variables that acts as guards in state transitions.

The other intended purpose is that the UserData type must provide a logger function of a predefined signature in case the state machine was generated with the trace command line parameter -Trace. This allows to trace the state transitions and event flow.

The second macro parameter allows to hand over another user defined type intended to realize a timer service. Whether the state machine needs and uses a timer service and how it is implemented is totally in the user's hand. A microwave oven example is provided in the samples folder. It implemets a basic timer service for realizing the timeout for the cooking time.

Note: presently there is no possibliy to avoid the handover of the UserData and Timer struct. This might change in later versions.

All further parameters are optional and simply added to the state machine struct. In the code above an example with parameters x and y is shown.

The complete example is available here. Built it with cargo build and then run it with cargo run. In the src folder you will find command line examples how to model the state machine and build the Rust code. If you run the code type in events and send them to the state machine handler by pressing return.

You should see this output on the command line:

cargo run
...
Oven demo program written in Rust
Type in one or more commands and then press return to send them to the state machine
Supported commands:
   o to open the door
   c to close the door
   + to increase the cooking time
   - to decrease the door
   P set oven power to high
   p set oven power to low

How to go on from here:

  • Download the codegen which includes all the examples
  • Modify the state machine model file and re-generate the Rust code again and explore your changes.
  • Read the manual about all the functions the code generator provides

Please note that the Rust back-end is work-in-progress. Your feedback is highly welcome!

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies
wiki/backends/rust_lang.txt · Last modified: 2024/05/03 22:42 by webmin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki