We are at the end of the journey. Today is the last day of the 2018 version of the Google Summer of Code.
So, here what I have done during this month of hard (and hot) work!
States Machine
The SM presented in the previous article has evolved to a newer and more complete version. The whole machine is defined through the following states and transitions:
# In controller.py
class Controller(object):
states = ["INIT", "STILL", "ERROR", "MOVING"]
transitions = [
{"trigger": "api_config", "source": "INIT", "dest": "STILL", "before": "setup_environment"},
{"trigger": "api_init", "source": "STILL", "dest": "INIT", "after": "api_config"},
{"trigger": "api_move", "source": "STILL", "dest": "MOVING", "conditions": "correct_inputs",
"after": "engine_move"},
{"trigger": "api_move", "source": "STILL", "dest": "ERROR", "unless": "correct_inputs",
"after": "handle_error"},
{"trigger": "api_error", "source": "STILL", "dest": "ERROR", "after": "handle_error"},
{"trigger": "engine_reached_destination", "source": "MOVING", "dest": "STILL",
"before": "check_position"},
{"trigger": "engine_fail", "source": "MOVING", "dest": "ERROR", "after": "handle_error"},
{"trigger": "error_solved", "source": "ERROR", "dest": "STILL", "after": "tell_position"},
{"trigger": "error_unsolved", "source": "ERROR", "dest": "INIT", "after": ["reconfig", "tell_position"]}
]
There are not many differences with the older graph but, behind the appearance, there is a lot of work. Now every arrow correspond to a series of defined actions, and the scheme was implemented as a real working program.
The structure of the Turnantenna’s brain
During the last week I worked on the refactoring of all the work done until that time. The final code is available in the new dedicated “refactor” branch on GitHub.
The States Machine above is implemented in the main process, which is able to communicate with 2 other processes: the engine driver and the RESTful server.
# In turnantenna.py
from multiprocessing import Process, Queue
from controller import Controller # import the states machine structure
from stepmotor import engine_main # import the engine process
from api import run # import the api process
def main():
engine_q = Queue()
api_q = Queue()
api_reader_p = Process(target=run, args=(api_q, ))
engine_p = Process(target=engine_main, args=(engine_q, ))
controller = Controller(api_q, engine_q) # start the SM
api_reader_p.start() # start the api process
engine_p.start() # start the engine process
controller.api_config()
The processes communicate with each other through messages in the queues. Messages are json, and have the following format:
{
'id': '1',
'dest': 'controller',
'command': 'move',
'parameter': angle
}
The “id” key is needed in order to control more than one engine, this is useful for future upgrades. “dest” specify the process that should read the message, and avoid wrong deliveries. “command” is the central content of the message, while “parameter” contains detailed (optional) informations.
Processes are infinite loops, where the queues are checked continuously. An example of this loop is:
# In api.py
from queue import Empty
while True:
try:
msg = queue.get(block=False)
if msg["dest"] != "api":
queue.put(msg) # send back the message
msg = None
except Empty:
msg = None
if msg and msg["id"] == "1":
command = msg["command"]
parameter = msg["parameter"]
if command == "known_command":
# do something
API
In order to interact with the turnantenna, I defined 3 methods: get_position(), init_engine() and move().
It is possible to call them through an HTTP request. A json needs to be attached to the request in order to make things work. In fact, APIs need some critical data: e.g. the id of the specific engine targeted, or a valid angle value to move the engine of that amount. If the request come without a json, or with a wrong one, the RESTful service respond with an error 400.
Here an example of input controls:
import requests
if not request.json or not 'id' in request.json:
abort(400)
id = request.json['id']
if id != '1': # still mono-engine
abort(404)
For the moment the system works with only one engine, but in the future it will be very simple to handle more motors
...
# if id != '1':
if id not in ('1', '2')
abort(404)
...
Final results
In these moths we started from an idea and a basic implementation, and we build-up a complete system ready to be tested. It is possible to see the Turnantenna logic run cloning the Turnantenna code from GitHub from the link Musuuu/punter_node_driver/tree/refactor.
Following the instructions in the readme file, you can run the turnantenna.py file and observe how it reacts to the HTTP requests made with curl.
The full documentation of the project could be found at turnantenna.readthedocs.io.
We are proud of the work done, and we’re ready to implement the whole system onto the hardware and make the Turnantenna turn!