Skip to content

Commit

Permalink
Add StateMachine to Controller
Browse files Browse the repository at this point in the history
  • Loading branch information
GDYendell committed Jun 21, 2016
1 parent e14c259 commit c3c046e
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 6 deletions.
62 changes: 61 additions & 1 deletion malcolm/core/controller.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import inspect
from collections import OrderedDict

from malcolm.core.loggable import Loggable
from malcolm.core.statemachine import StateMachine
from malcolm.core.attribute import Attribute
from malcolm.core.enummeta import EnumMeta
from malcolm.core.stringmeta import StringMeta
from malcolm.core.booleanmeta import BooleanMeta


@StateMachine.insert
class Controller(Loggable):
"""Implement the logic that takes a Block through its statemachine"""

Expand All @@ -13,6 +20,16 @@ def __init__(self, block):
"""
logger_name = "%s.controller" % block.name
super(Controller, self).__init__(logger_name)

enum_meta = EnumMeta("State", "State of Block", [])
self.state = Attribute(enum_meta)
string_meta = StringMeta("Status", "Status of Block")
self.status = Attribute(string_meta)
boolean_meta = BooleanMeta("Busy", "Whether Block busy or not")
self.busy = Attribute(boolean_meta)

self.writeable_methods = OrderedDict()

self.block = block
for attribute in self.create_attributes():
block.add_attribute(attribute)
Expand All @@ -35,7 +52,6 @@ def create_methods(self):
member.Method.set_function(member)
yield member.Method


def create_attributes(self):
"""Abstract method that should provide Attribute instances for Block
Expand All @@ -44,3 +60,47 @@ def create_attributes(self):
block.add_attribute(attribute)
"""
return iter(())

def transition(self, state, message):
"""
Change to a new state if the transition is allowed
Args:
state(str): State to transition to
message(str): Status message
"""

if self.stateMachine.is_allowed(initial_state=self.state.value,
target_state=state):

self.state.set_value(state)

if state in self.stateMachine.busy_states:
self.busy.set_value(True)
else:
self.busy.set_value(False)

self.status.set_value(message)

for method in self.block._methods.values():
if method in self.writeable_methods[state]:
self.block._methods[method].set_writeable(True)
else:
self.block._methods[method].set_writeable(False)

self.block.notify_subscribers()

else:
raise TypeError("Cannot transition from %s to %s" %
(self.state.value, state))

def set_writeable_methods(self, state, methods):
"""
Set the methods that can be changed in the given state
Args:
state(list(str)): States to set writeable
methods(Method): Method to set states for
"""

self.writeable_methods[state] = [method.name for method in methods]
49 changes: 44 additions & 5 deletions tests/test_core/test_controller.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import unittest

from . import util
import util
from mock import MagicMock, call

from collections import OrderedDict

# module imports
from malcolm.core.controller import Controller

Expand All @@ -19,12 +20,50 @@ def say_goodbye(self, name):

class TestController(unittest.TestCase):

def setUp(self):
b = MagicMock()
b._methods.values.return_value = ["say_hello", "say_goodbye"]
self.m1 = MagicMock()
self.m2 = MagicMock()
b._methods.__getitem__.side_effect = [self.m1, self.m2]
self.c = DummyController(b)

def test_init(self):
b = MagicMock()
c = DummyController(b)
self.assertEqual(c.block, b)
self.c = DummyController(b)
self.assertEqual(self.c.block, b)
b.add_method.assert_has_calls(
[call(c.say_goodbye.Method), call(c.say_hello.Method)])
[call(self.c.say_goodbye.Method), call(self.c.say_hello.Method)])

self.assertEqual(self.c.state.name, "State")
self.assertEqual(self.c.state.meta.metaOf, "malcolm:core/Enum:1.0")
self.assertEqual(self.c.status.name, "Status")
self.assertEqual(self.c.status.meta.metaOf, "malcolm:core/String:1.0")
self.assertEqual(self.c.busy.name, "Busy")
self.assertEqual(self.c.busy.meta.metaOf, "malcolm:core/Boolean:1.0")

self.assertEqual(OrderedDict(), self.c.writeable_methods)

def test_transition(self):
self.c.writeable_methods["Configure"] = "say_hello"
self.c.stateMachine.allowed_transitions = dict(Idle="Configure")
self.c.state.value = "Idle"
self.c.stateMachine.busy_states = ["Configure"]

self.c.transition("Configure", "Attempting to configure scan...")

self.assertEqual("Configure", self.c.state.value)
self.assertEqual("Attempting to configure scan...", self.c.status.value)
self.assertTrue(self.c.busy.value)
self.m1.set_writeable.assert_called_once_with(True)
self.m2.set_writeable.assert_called_once_with(False)

def test_set_writeable_methods(self):
m = MagicMock()
m.name = "configure"
self.c.set_writeable_methods("Idle", [m])

self.assertEqual(["configure"], self.c.writeable_methods['Idle'])

if __name__ == "__main__":
unittest.main(verbosity=2)

0 comments on commit c3c046e

Please sign in to comment.