Here are som pictures and videos of the robot:
In order to validate if the idea about cross-cutting transitions (or global transitions) is better in terms of lower complexity in the state machine two master students where given the assignment to program a robot in ThingML. The robot is Arduino controlled, it have a mortor shield, two dc motors, three bumper sensors, three range sensors, a sound module, two speakers and a lot of LEDs.
The robot is suposed to go forward and adjust the speed depending on the range to the object in front. If it senses things from the sides it shall divert and go in an oposite direction. If the robot crash it should back up a little, find a new direction and make some sound to signal that it is not happy with crashing. The LEDs can be used as the students want.
The students where given a precode with all the sensors and things implemented and what they had to do was to define the states, the actions in the states and the transitions. By combining these three elements the behaviour of the robot can be defined.
In the first part of the case study the two master students wrote their own implementation of the robot. The first students program had eigth states and 28 transitions, the source can be seen here:
import "Devices/RangeSensor2.thingml" import "Devices/MotorShieldDFduino.thingml" import "Devices/LightResitorArray.thingml" import "Devices/somo-14d.thingml" import "Devices/Bumper.thingml" import "../../../core/timer.thingml" import "Devices/LightChain.thingml" thing BigRobot includes RangeMsgs, MotorShieldMsgs, TimerMsgs, LightArrayMsgs, SoundMsgs, BumperMsgs, LightChainMsgs { required port Sounds{ sends r2d2 //sends chrash //sends happy //sends cranky //sends set_sound //set sound with 1 2 3 or 4 //sends play_set_sound sends stop_sound } required port Bumper { receives bump } required port LightIn{ receives forward_dir receives left_dir receives right_dir receives dont_know_dir } required port Motor { sends forward_fast //sends forward_medium sends forward_slow sends stop //sends backwards_fast sends backward_slow sends right sends left //sends set_motors_speed } required port Timer{ sends timer_start receives timer_timeout } required port Robot { sends get_range receives range } required port Light{ sends start_green sends start_blue sends stop_green sends stop_blue sends blink //sends fade_blue //sends fade_green sends start_crazy sends stop_crazy } property range : UInt16 = 0 statechart BigRobotImpl init idle { state idle { on entry do Timer!timer_start(500) Motor!stop() Light!stop_crazy() Sounds!stop_sound() end transition -> run event Timer?timer_timeout } state run{ transition -> forward event LightIn?forward_dir transition-> stop event LightIn?dont_know_dir transition -> crashed event Bumper?bump transition -> left event LightIn?left_dir transition -> right event LightIn?right_dir } state forward { on entry do if(range > 75) do Motor!forward_fast() Light!stop_green() Light!stop_blue() end if(range > 25 and range < 75) do Motor!forward_slow() Light!stop_blue() Light!start_green() end end on exit do Light!stop_green() Light!stop_blue() end transition-> stop event LightIn?forward_sir guard range < 25 transition -> forward event LightIn?forward_dir transition-> stop event LightIn?dont_know_dir transition -> crashed event Bumper?bump transition -> left event LightIn?left_dir transition -> right event LightIn?right_dir } state stop { on entry do Light!start_crazy() Sounds!r2d2 () if(range < 15) do Motor!stop() end end on exit do Light!stop_crazy() end transition -> forward event LightIn?forward_dir transition-> stop event LightIn?dont_know_dir transition -> crashed event Bumper?bump transition -> left event LightIn?left_dir transition -> right event LightIn?right_dir } state left { on entry do Motor!left() end transition -> forward event LightIn?forward_dir transition-> stop event LightIn?dont_know_dir transition -> crashed event Bumper?bump transition -> left event LightIn?left_dir transition -> right event LightIn?right_dir } state right { on entry do Motor!right() end transition -> forward event LightIn?forward_dir transition-> stop event LightIn?dont_know_dir transition -> crashed event Bumper?bump transition -> left event LightIn?left_dir transition -> right event LightIn?right_dir } state crashed { property time : UInt16 = 0 on entry do time = 400 Timer!timer_start(time) Light!start_crazy() Motor!backward_slow() end internal event Timer?timer_timeout guard time == 400 action do time = 500 Timer!timer_start(time) Motor!right() end transition -> run event Timer?timer_timeout guard time == 500 action do Motor!stop() Light!stop_crazy() end } region Measure init MeasureDistance { state MeasureDistance{ on entry do Robot!get_range() end internal event m : Robot?range action do range = m.cm Robot!get_range() end } } } }
Student 2's program contained 9 states and 23 transitions and the source code looks like this:
import "Devices/RangeSensor2.thingml" import "Devices/MotorShieldDFduino.thingml" import "Devices/LightResitorArray.thingml" import "Devices/somo-14d.thingml" import "Devices/Bumper.thingml" import "../../../core/timer.thingml" import "Devices/LightChain.thingml" thing BigRobot includes RangeMsgs, MotorShieldMsgs, TimerMsgs, LightArrayMsgs, SoundMsgs, BumperMsgs, LightChainMsgs { required port Sounds{ //sends r2d2 sends chrash sends happy sends cranky sends set_sound //set sound with 1 2 3 or 4 sends play_set_sound sends stop_sound } required port Bumper { receives bump } required port LightIn{ receives forward_dir receives left_dir receives right_dir receives dont_know_dir } required port Motor { sends forward_fast sends forward_medium sends forward_slow sends stop sends backwards_fast sends backward_slow sends right sends left // sends set_motors_speed } required port Timer{ sends timer_start receives timer_timeout } required port Robot { sends get_range receives range } required port Light{ sends start_green sends start_blue sends stop_green sends stop_blue sends blink sends fade_blue sends fade_green sends start_crazy sends stop_crazy } property range : UInt16 = 0 property rand : UInt16 = 0 statechart BigRobotImpl init Stop { state Start{ on entry do Antenna!start () //er dette riktig? Light!stop_crazy () Light!stop_blue () Light!start_green () Sounds!happy () end transition -> Drivefast event LightIn?forward_dir guard range > 30 transition -> Driveslow event LightIn?forward_dir guard range < 30 transition -> Turnleft event LightIn?left_dir transition -> Turnright event LightIn?right_dir transition -> Stop event LightIn?dont_know_dir } state Stop { on entry do Motor!stop () Timer!timer_start (1000) Light!stop_green () Light!stop_blue () Light!stop_crazy () Sounds!cranky () end transition -> Start event Timer?timer_timeout } state Drivefast { on entry Motor!forward_fast () Light!stop_blue () Light!stop_crazy () Light!start_green () end transition -> Driveslow event LightIn?forward_dir guard range < 30 transition -> Drivefast event LightIn?forward_dir transition -> Turnleft event LightIn?left_dir transition -> Turnright event LightIn?right_dir transition -> Stop event LightIn?dont_know_dir transition -> Chrash event Bumper?bump } state Driveslow { on entry do Motor!forward_slow () Light!stop_blue () Light!stop_crazy () Light!start_green () end transition -> Drivefast event LightIn?forward_dir guard range > 30 transition -> Backwards event LightIn?forward_dir guard range < 20 transition -> Driveslow event LightIn?forward_dir guard range < 30 transition -> Turnleft event LightIn?left_dir transition -> Turnright event LightIn?right_dir transition -> Stop event LightIn?dont_know_diR transition -> Chrash event Bumper?bump } state Chrash { on entry do Timer!timer_start (500) Motor!stop () Sounds!chrash () Light!stop_green () Light!stop_blue () Light!start_crazy () end transition -> Backwards event Timer?timer_timeout } state Backwards { on entry do Timer!timer_start (750) Motor!backwards_fast () Light!stop_crazy () Light!stop_green () Light!start_blue () if (range < 20) do Motor!backward_slow () Light!fade_blue () end rand = 'random(3);' if(rand == 1)do rand = 'random(2);' if (rand == 0) do Motor!left () end if(rand == 1) do Motor!right () end end end transition -> Start event Timer?timer_timeout } state Turnleft { on entry do Timer!timer_start (750) Motor!left () Light!stop_crazy () Light!stop_green () Light!start_blue () Sounds!set_sound (1) Sounds!play_set_sound () end transition -> Start event Timer?timer_timeout } state Turnright { on entry do Timer!timer_start (750) Motor!right () Light!stop_crazy () Light!stop_green () Light!start_blue () Sounds!set_sound (2) Sounds!play_set_sound () end transition -> Start event Timer?timer_timeout } region Measure init MeasureDistance { state MeasureDistance{ on entry do Robot!get_range() end internal event m : Robot?range action do range = m.cm Robot!get_range() end } } } }
After writing this code both students where introdused with the consept og global transitions, and they where asked to rewrite their programs in according to the new set of rules. Then both students could remove one state from their program, and they got a lot less transitions. Student 1 ended up with only seven transitions and student 2 ended up with eight transitions. This means that the cyclomatic complexity will be a lot lower for the new programs. Student 1's program can be viewed here:
import "Devices/RangeSensor2.thingml" import "Devices/MotorShieldDFduino.thingml" import "Devices/LightResitorArray.thingml" import "Devices/somo-14d.thingml" import "Devices/Bumper.thingml" import "../../../core/timer.thingml" import "Devices/LightChain.thingml" thing BigRobot includes RangeMsgs, MotorShieldMsgs, TimerMsgs, LightArrayMsgs, SoundMsgs, BumperMsgs, LightChainMsgs { required port Sounds { sends r2d2 //sends chrash //sends happy //sends cranky //sends set_sound //set sound with 1 2 3 or 4 //sends play_set_sound sends stop_sound } required port Bumper { receives bump } required port LightIn{ receives forward_dir receives left_dir receives right_dir receives dont_know_dir } required port Motor { sends forward_fast //sends forward_medium sends forward_slow sends stop //sends backwards_fast sends backward_slow sends right sends left //sends set_motors_speed } required port Timer{ sends timer_start receives timer_timeout } required port Robot { sends get_range receives range } required port Light { sends start_green sends start_blue sends stop_green sends stop_blue sends blink //sends fade_blue //sends fade_green sends start_crazy sends stop_crazy } property range : UInt16 = 0 statechart BigRobotImpl init idle { transitions LightIn?forward_dir { (range > 25) -> forward -> stop } crashed transitions LightIn?dont_know_dir { -> stop } crashed transitions LightIn?left_dir { -> left } crashed transitions LightIn?right_dir { -> right } crashed transitions Bumper?bump { -> crashed } state idle { on entry do Motor!stop() Light!stop_crazy() Sounds!stop_sound() end } state forward { on entry do if(range > 75) do Motor!forward_fast() Light!stop_green() Light!stop_blue() end if(range > 25 and range < 75) do Motor!forward_slow() Light!stop_blue() Light!start_green() end end on exit do Light!stop_green() Light!stop_blue() end } state stop { on entry do Light!start_crazy() Sounds!r2d2 () if(range < 15) do Motor!stop() end end on exit do Light!stop_crazy() end } state left { on entry do Motor!left() end } state right { on entry do Motor!right() end } state crashed { property time : UInt16 = 0 on entry do time = 400 Timer!timer_start(time) Light!start_crazy() Motor!backward_slow() end internal event Timer?timer_timeout guard time == 400 action do time = 500 Timer!timer_start(time) Motor!right() end transitions -> forward event Timer?timer_timeout guard time == 500 action do Motor!stop() Light!stop_crazy() end } region Measure init MeasureDistance { state MeasureDistance{ on entry do Robot!get_range() end internal event m : Robot?range action do range = m.cm Robot!get_range() end } } } }
Student 2's improved program can be seen here:
import "Devices/RangeSensor2.thingml" import "Devices/MotorShieldDFduino.thingml" import "Devices/LightResitorArray.thingml" import "Devices/somo-14d.thingml" import "Devices/Bumper.thingml" import "../../../core/timer.thingml" import "Devices/LightChain.thingml" thing BigRobot includes RangeMsgs, MotorShieldMsgs, TimerMsgs, LightArrayMsgs, SoundMsgs, BumperMsgs, LightChainMsgs { required port Sounds{ //sends r2d2 sends chrash sends happy sends cranky sends set_sound //set sound with 1 2 3 or 4 sends play_set_sound sends stop_sound } required port Bumper { receives bump } required port LightIn{ receives forward_dir receives left_dir receives right_dir receives dont_know_dir } required port Motor { sends forward_fast sends forward_medium sends forward_slow sends stop sends backwards_fast sends backward_slow sends right sends left // sends set_motors_speed } required port Timer{ sends timer_start receives timer_timeout } required port Robot { sends get_range receives range } required port Light{ sends start_green sends start_blue sends stop_green sends stop_blue sends blink sends fade_blue sends fade_green sends start_crazy sends stop_crazy } property range : UInt16 = 0 property rand : UInt16 = 0 statechart BigRobotImpl init Stop { transitions m : LightIn?forward_dir { (range > 30) -> Drivefast (range < 30) -> Driveslow (range < 20) -> Backwards } Chrash transitions m : LightIn?left_dir { -> Turnleft, Chrash } transitions m : LightIn?right_dir { -> Turnright, Chrash } transitions m : LightIn?dont_know_dir { -> Stop, Chrash } transitions m : Bumper?bump { -> Chrash } state Stop { on entry do Motor!stop () Light!stop_green () Light!stop_blue () Light!stop_crazy () Sounds!cranky () end } state Drivefast { on entry do Motor!forward_fast () Light!stop_blue () Light!stop_crazy () Light!start_green () end } state Driveslow { on entry do Motor!forward_slow () Light!stop_blue () Light!stop_crazy () Light!start_green () end } state Chrash { on entry do Timer!timer_start (500) Motor!stop () Sounds!chrash () Light!stop_green () Light!stop_blue () Light!start_crazy () end transition -> Backwards event Timer?timer_timeout } state Backwards { on entry do Motor!backwards_fast () Light!stop_crazy () Light!stop_green () Light!start_blue () if (range < 20) do Motor!backward_slow () Light!fade_blue () end rand = 'random(3);' if(rand == 1) do rand = 'random(2);' if (rand == 0) do Motor!left () end if(rand == 1) do Motor!right () end end end } state Turnleft { on entry do Motor!left () Light!stop_crazy () Light!stop_green () Light!start_blue () Sounds!set_sound (1) Sounds!play_set_sound () end } state Turnright { on entry do Motor!right () Light!stop_crazy () Light!stop_green () Light!start_blue () Sounds!set_sound (2) Sounds!play_set_sound () end } region Measure init MeasureDistance { state MeasureDistance{ on entry do Robot!get_range() end internal event m : Robot?range action do range = m.cm Robot!get_range() end } } } }
The linecount in the second program dropped with aproximately with 20% for both students. This means that there is less code to write while using global transitions, and therefore the programs can be written faster and is easier to maintian over time.
Here is the complete source code to run the program yourself Attach:bigrobotsource.zip