Commit 299496c6 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Merge https://github.com/esa/opengeode into python3-pyside2

parents 9559e025 2c9f5c60
......@@ -58,7 +58,9 @@ full-install: update
$(MAKE) install
publish:
@python3 setup.py sdist upload
@rm -f dist/*
@python3 setup.py sdist bdist_wheel
@twine upload dist/*
pytest:
# make sure you have installed pytest-qt:
......
......@@ -53,50 +53,17 @@ Debian 10 (buster) is the baseline. Recent versions of Ubuntu (20.x) should work
Using TASTE
-----------
__Important: OpenGEODE is already installed in the TASTE Virtual Machine, and fully integrated with the toolset, however, the current TASTE VM is a bit old - it is based on Debian 9 which does not have important dependencies to support the latest version of OpenGEODE. It is missing Python 3.7+ and PySide2. An upgrade is in the works, but it's not ready yet. If you are familiar enough with Linux, you can manually upgrade by completing the following steps.__
__Important: OpenGEODE is already installed in the TASTE10 Virtual Machine (based on Debian Buster), and fully integrated with the toolset. It is the easiest way to get started with OpenGEODE__
_Install the [TASTE VM](https://taste.tools/#install). Once logged in, enter a new shell and:_
To start a new project run:
```
# Switch to root
sudo su
# Make sure VM image is up to date
apt-get update
apt-get upgrade
apt-get dist-upgrade
# Point APT to Buster to prepare for upgrade
sed -i 's/stretch/buster/g' /etc/apt/sources.list
# Upgrade packages to Buster; when prompted allow services to be restarted automatically
apt-get upgrade
# Upgrade distribution to Buster
apt-get dist-upgrade
# Remove packages no longer needed
apt-get autoremove
# Return to taste user; exit root
exit
# Navigate to TASTE source; upgrade
cd ~/tool-src
git pull
git checkout feature_buster
./Update-TASTE.sh
$ taste
```
After this upgrade, you can work with the latest version of the tools, in particular the new Kazoo build system and Opengeode 3.xx (which you can update at any time). The Quick Reference Card has not been updated to reflect this yet.
The main differences to create/edit a project is that you must just run `taste<` (and not `taste-create-project/taste-edit-project` anymore). To build, run `make`.
Select a project name and the graphical editor will pop-up shortly after. You can add functions to the system and specify the imnplementation language to __SDL__. When you edit the function, the OpenGEODE editor will start.
You can check an example of a system using Opengeode if you go in `~/tool-src/kazoo/tests/Demo_ABB_Opengeode` and run `make` to build it. Then `taste` to edit.
In the interface view, select the SDL language for the implementation of the blocks you want to model using OpenGEODE.
Then when you right-click on the SDL block you can select the option "Open SDL Editor".
The code is automatically generated when you exit the tool.
Manual
......@@ -138,7 +105,7 @@ Once you have the dependencies installed you can update the tool by running the
```
$ git pull
$ make install # alternatively: pip3 install --user --upgrade .
$ make install # alternatively: pip3 install --user --upgrade opengeode
```
OpenGEODE Website
......@@ -175,6 +142,18 @@ The background pattern was downloaded from www.subtlepatterns.com
Changelog
=========
**3.3.2 (10/2020)**
- Fix reporting of semantic errors in procedures
**3.3.1 (09/2020)**
- Fix issue with type synonyms
- Update installation procedure
- Enable pip3 installations from PyPI
**3.3.0 (08/2020)**
- Save the state as an ASN.1 model instead of a native Ada type
- Ada backend basic support for "decision any"
**3.2.3 (09/2020)
- Fix type checks when a type inherits another type with different constraints
......
......@@ -61,7 +61,7 @@
this pattern is straightforward, once the generate function for each AST
entry is properly implemented).
Copyright (c) 2012-2019 European Space Agency
Copyright (c) 2012-2020 European Space Agency
Designed and implemented by Maxime Perrotin
......@@ -96,7 +96,8 @@ SHARED_LIB = False
#SEPARATOR = u'\u00dc'
# Avoid Unicode characters, they cause occasional annoying issues
SEPARATOR = "_0_"
LPREFIX = u'ctxt'
LPREFIX = 'ctxt'
ASN1SCC = 'asn1Scc'
def is_numeric(string):
......@@ -115,36 +116,32 @@ def external_ri_list(process):
result = []
#print process.fpar
for signal in process.output_signals:
param_name = signal.get('param_name') \
or u'{}_param'.format(signal['name'])
param_name = signal.get('param_name') or f'{signal["name"]}_param'
param_spec = ''
if 'type' in signal:
typename = type_name(signal['type'])
param_spec = u'({pName}: access {sort})'.format(pName=param_name,
sort=typename)
result.append(u"procedure RI{sep}{name}{param}".format(sep=SEPARATOR,
name=signal['name'],
param=param_spec))
param_spec = f'({param_name}: in out {typename})'
result.append(f"procedure RI{SEPARATOR}{signal['name']}{param_spec}")
for proc in (proc for proc in process.procedures if proc.external):
ri_header = u'procedure RI{sep}{sig_name}'.format(
sep=SEPARATOR,
sig_name=proc.inputString)
ri_header = f'procedure RI{SEPARATOR}{proc.inputString}'
params = []
params_spec = ''
for param in proc.fpar:
typename = type_name(param['type'])
params.append(u'{par[name]}: access {sort}'.format(par=param,
sort=typename))
if param['direction'] == 'in':
direct = 'in out'
else:
direct = 'out'
params.append(f'{param["name"]} : {direct} {typename}')
if params:
params_spec = u"({})".format("; ".join(params))
ri_header += params_spec
result.append(ri_header)
for timer in process.timers:
result.append(u"procedure Set_{}(val: access asn1SccT_Uint32)"
.format(timer))
result.append(u"procedure Reset_{}"
.format(timer))
result.append(
f"procedure Set_{timer} (Val : in out {ASN1SCC}T_Uint32)")
result.append(f"procedure Reset_{timer}")
return result
......@@ -178,7 +175,7 @@ def _process(process, simu=False, instance=False, taste=False, **kwargs):
# First copy the list of timers to the instance (otherwise the
# instance would miss some PIs and RIs to set the actual timers)
process_instance.timers = process.timers
generate(process_instance, simu, instance=True)
generate(process_instance, simu, instance=True, taste=taste)
global TYPES
TYPES = process.dataview
......@@ -192,6 +189,9 @@ def _process(process, simu=False, instance=False, taste=False, **kwargs):
SHARED_LIB = True
LPREFIX = process_name + u'_ctxt'
for each in PROCEDURES:
process.random_generator.update(each.random_generator)
# taste-properties module-specific flag for the Ada backend:
# import the state data from an external module
import_context = kwargs["ppty_check"] if "ppty_check" in kwargs else ""
......@@ -238,9 +238,11 @@ def _process(process, simu=False, instance=False, taste=False, **kwargs):
parent = parent.parent
if isinstance(parent, ogAST.System):
parent = parent.ast
asn1_filenames = ' '.join(parent.asn1_filenames)
asn1_filenames = (f"{' '.join(parent.asn1_filenames)} "
f"{process_name.lower()}_datamodel.asn")
asn1_uniq = ' '.join(each for each in parent.asn1_filenames
if not each.endswith('dataview-uniq.asn'))
asn1_uniq += f" {process_name.lower()}_datamodel.asn"
pr_path = ' '.join(parent.pr_files) if None not in parent.pr_files else ''
pr_names = ' '.join(
os.path.basename(pr_file) for pr_file in parent.pr_files)
......@@ -270,34 +272,26 @@ end {pr}_Lib;'''.format(pr=process_name.lower(),
for Object_Dir use "../obj";
end {pr}_Ada;'''.format(pr=process_name.lower())
simu_script = '''#!/bin/bash -e
#rm -rf {pr}_simu
pr = process_name.lower()
simu_script = f'''#!/bin/bash -e
mkdir -p {pr}_simu
cp {pr_path} {asn1} {pr}_simu
cp {pr_path} {asn1_filenames} {pr}_simu
cd {pr}_simu
opengeode {pr_names} --shared
cat {uniq} >> dataview-uniq.asn '''.format(pr=process_name.lower(),
asn1=asn1_filenames,
pr_path=pr_path,
uniq=asn1_uniq or '/dev/null',
pr_names=pr_names)
if asn1_filenames:
simu_script += '''
mono $(which asn1.exe) -Ada -typePrefix asn1Scc -equal {asn1}
mono $(which asn1.exe) -c -typePrefix asn1Scc -equal {asn1}'''.format(
asn1=asn1_filenames)
simu_script += '''
gprbuild -p -P {pr}_lib.gpr
cat {asn1_uniq} >> dataview-uniq.asn
mono $(which asn1.exe) -Ada -typePrefix {ASN1SCC} -equal dataview-uniq.asn
mono $(which asn1.exe) -c -typePrefix {ASN1SCC} -equal dataview-uniq.asn
gprbuild -p -P {process_name.lower()}_lib.gpr
rm -f dataview-uniq.c dataview-uniq.h
asn2aadlPlus dataview-uniq.asn DataView.aadl
aadl2glueC DataView.aadl {pr}_interface.aadl
aadl2glueC DataView.aadl {process_name.lower()}_interface.aadl
asn2dataModel -toPython dataview-uniq.asn
make -f Makefile.python
echo "errCodes=$(taste-asn1-errCodes ./dataview-uniq.h)" >>datamodel.py
LD_LIBRARY_PATH=./lib:. opengeode-simulator
'''.format(pr=process_name.lower())
'''
LOG.info('Generating Ada code for process ' + str(process_name))
......@@ -327,20 +321,38 @@ LD_LIBRARY_PATH=./lib:. opengeode-simulator
(name for name in process.mapping.keys()
if not name.endswith(u'START'))))
reduced_statelist = {s for s in full_statelist if s not in parallel_states}
if aggregates:
# Parallel states in a state aggregation may terminate
full_statelist.add(u'state{}end'.format(SEPARATOR))
full_statelist.add(f'state{SEPARATOR}end')
# Format the state list with ASN.1-compatible syntax
process_asn1 = process_name.upper().replace('_', '-')
statelist_asn1 = (state.lower().replace('_', '-')
for state in full_statelist)
states_asn1 = ", ".join(statelist_asn1) \
or f'{process_asn1.lower()}-has-no-state'
# Create an ASN.1 definition of the list of states, instead of
# a native Ada type - this allows external tools to access
# information about the model without having to parse SDL
asn1_states_def = f"{process_asn1}-States ::= ENUMERATED" \
f" {{{states_asn1}}}"
context_decl = []
if full_statelist and not import_context:
# don't generate state type in stop condition automaton
context_decl.append(u'type States is ({});'
.format(u', '.join(full_statelist) or u'No_State'))
# Generate ASN.1 model for the NEWTYPEs (types defined in SDL)
# Generate an ASN.1 model containing the state definition, as well as
# all the user-defined SDL type (NEWTYPEs...)
asn1_template = [f'{process_asn1}-Datamodel DEFINITIONS ::=',
'BEGIN']
# When a signal is sent from the model a call to a function is emitted
# This function has to be provided - either by TASTE (kazoo), or by
# the user. Opengeode will generate a stub package for this.
ri_stub_ads = []
ri_stub_adb = []
# Add user-defined NEWTYPEs
if process.user_defined_types:
asn1_template = [u'{}-Newtypes DEFINITIONS ::='.format(
process_name.upper().replace('_', '-')), 'BEGIN']
types_with_proper_case = []
# The ASN.1 module must import the types from other asn1 modules
......@@ -369,9 +381,15 @@ LD_LIBRARY_PATH=./lib:. opengeode-simulator
rangeMin=rangeMin,
rangeMax=rangeMax,
refType=refTypeCase.replace('_', '-')))
asn1_template.append('END')
with open(process_name + '_newtypes.asn', 'w') as asn1_file:
asn1_file.write('\n'.join(asn1_template))
if not import_context:
# don't generate state type in Stop Condition/Observer
# automaton as it is defined in the observed process
asn1_template.append(asn1_states_def)
asn1_template.append('END')
# Write the ASN.1 file
with open(process_name.lower() + '_datamodel.asn', 'w') as asn1_file:
asn1_file.write('\n'.join(asn1_template))
# Generate the code to declare process-level context
if not import_context:
......@@ -380,15 +398,16 @@ LD_LIBRARY_PATH=./lib:. opengeode-simulator
context_decl.extend([f'type {LPREFIX}_Ty is', 'record'])
if full_statelist:
context_decl.append('State : States;')
context_decl.append(f'State : {ASN1SCC}{process_name}_States;')
context_decl.append('Init_Done : Boolean := False;')
# State aggregation: add list of substates
for substates in aggregates.values():
for each in substates:
context_decl.append('{}{}state: States;'
.format(each.statename, SEPARATOR))
context_decl.append(
f'{each.statename}{SEPARATOR}state:'
f' {ASN1SCC}{process_name}_States;')
for var_name, (var_type, def_value) in process.variables.items():
if def_value:
......@@ -401,8 +420,9 @@ LD_LIBRARY_PATH=./lib:. opengeode-simulator
assert not dst and not dlocal,\
'DCL: Expecting a ground expression'
context_decl.append(
'{n} : aliased {sort}{default};'
'{n} : {aliased}{sort}{default};'
.format(n=var_name,
aliased="aliased " if simu else "",
sort=type_name(var_type),
default=' := ' + dstr if def_value else ''))
......@@ -414,8 +434,8 @@ LD_LIBRARY_PATH=./lib:. opengeode-simulator
f'{LPREFIX}: {import_context}.{import_context}_Ctxt_Ty '
f'renames {import_context}.{import_context}_ctxt;')
else:
context_decl.append('{ctxt}: aliased {ctxt}_Ty;'.format(ctxt=LPREFIX))
if simu:
context_decl.append('{ctxt} : {ctxt}_Ty;'.format(ctxt=LPREFIX))
if simu and not import_context:
# Export the context, so that it can be manipulated from outside
# (in practice used by the "properties" module.
context_decl.append(u'pragma export (C, {ctxt}, "{ctxt}");'
......@@ -446,36 +466,44 @@ LD_LIBRARY_PATH=./lib:. opengeode-simulator
# Declare start procedure for aggregate states XXX add in C generator
# should create one START per "via" clause, TODO later
for name, substates in aggregates.items():
proc_name = u'procedure {}{}START'.format(name, SEPARATOR)
process_level_decl.append(u'{};'.format(proc_name))
aggreg_start_proc.extend([u'{} is'.format(proc_name),
proc_name = f'procedure {name}{SEPARATOR}START'
process_level_decl.append(f'{proc_name};')
aggreg_start_proc.extend([f'{proc_name} is',
'begin'])
aggreg_start_proc.extend(u'Execute_Transition ({sub}{sep}START);'
.format(sub=subname.statename,
sep=SEPARATOR)
for subname in substates)
aggreg_start_proc.extend([u'end {}{}START;'
.format(name, SEPARATOR),
aggreg_start_proc.extend([f'end {name}{SEPARATOR}START;',
'\n'])
# Add the declaration of the Execute_Transition procedure
process_level_decl.append(
'procedure Execute_Transition (Id: Integer);')
'procedure Execute_Transition (Id : Integer);')
# Generate the code of the start transition (if process not empty)
Init_Done = u'{ctxt}.Init_Done := True;'.format(ctxt=LPREFIX)
if not simu:
start_transition = [u'begin']
if process.transitions:
start_transition.append(u'Execute_Transition (0);')
start_transition.append(Init_Done)
else:
start_transition = ['procedure Startup is',
'begin',
' Execute_Transition (0);'
if process.transitions else 'null;',
Init_Done,
'end Startup;']
Init_Done = f'{LPREFIX}.Init_Done := True;'
# if not simu:
# start_transition = ['begin']
# if process.transitions:
# start_transition.append('Execute_Transition (0);')
# start_transition.append(Init_Done)
# else:
rand_reset_decl = []
for rand_g in process.random_generator:
rand_reset_decl.append(f'Rand_{rand_g}_Pkg.Reset (Gen_{rand_g});');
start_transition = [
'procedure Startup is',
'begin',
*rand_reset_decl,
'Execute_Transition (0);'
if process.transitions else 'null;',
Init_Done,
'end Startup;',
'',
'begin',
'Startup;']
# Generate the TASTE template
try:
......@@ -487,10 +515,6 @@ LD_LIBRARY_PATH=./lib:. opengeode-simulator
except TypeError:
asn1_modules = '-- No ASN.1 data types are used in this model'
include_custom_types = u'''with {process_name}_newtypes;
use {process_name}_newtypes;'''.format(process_name=process_name) \
if process.user_defined_types else u''
taste_template = [f'''\
-- This file was generated automatically by OpenGEODE: DO NOT MODIFY IT !
......@@ -521,6 +545,26 @@ package body {process_name} is'''
imp_str = f"with {import_context}; use {import_context};" \
if import_context else ''
imp_datamodel = (f"with {process_name}_Datamodel; "
f"use {process_name}_Datamodel;") \
if not import_context and not instance else (
f"with {import_context}_Datamodel; "
f"use {import_context}_Datamodel;"
if import_context else "")
imp_ri = f"with {process_name}_RI;" if not simu and not generic else ""
rand = ('with Ada.Numerics.Discrete_Random;'
if process.random_generator else "")
rand_decl = []
for each in process.random_generator:
rand_decl.extend([
f'type Rand_{each}_ty is new Integer range 1 .. {each};',
f'package Rand_{each}_Pkg is new Ada.Numerics.Discrete_Random'\
f' (Rand_{each}_ty);',
f'Gen_{each} : Rand_{each}_Pkg.Generator;',
f'Num_{each} : Rand_{each}_ty;'])
ads_template = [f'''\
-- This file was generated automatically by OpenGEODE: DO NOT MODIFY IT !
......@@ -533,23 +577,43 @@ use Interfaces,
Ada.Characters.Handling;
{asn1_modules}
{include_custom_types}
{imp_datamodel}
{imp_str}
{imp_ri}
{instance_decl}
{rand}
{generic_spec}'''.strip() + f'''
package {process_name} with Elaborate_Body is''']
ri_stub_ads = [f'''\
-- This file is a stub for the implementation of the required interfaces
-- It is normally overwritten by TASTE with the actual connection to the
-- middleware. If you use Opengeode independently from TASTE you must
-- edit the .adb (body) with your own implementation of these functions.
-- The body stub will be generated only once.
{asn1_modules}
package {process_name}_RI is''']
ri_stub_adb = [f'''-- Stub generated by OpenGEODE.
-- You can edit this file, it will not be overwritten
package body {process_name}_RI is''']
dll_api = []
ads_template.extend(rand_decl)
if not instance:
ads_template.extend(context_decl)
if not generic and not instance:
if not generic and not instance and not import_context:
# Add function allowing to trace current state as a string
ads_template.append(
f"function Get_State return chars_ptr "
f"is (New_String (States'Image ({LPREFIX}.State)))"
f"is (New_String "
f"({ASN1SCC}{process_name}_States'Image ({LPREFIX}.State)))"
f" with Export, Convention => C, "
f'Link_Name => "{process_name.lower()}_state";')
if simu:
if simu and not import_context: # import_context = stop condition
ads_template.append('-- API for simulation via DLL')
dll_api.append('-- API to remotely change internal data')
set_state_decl = "procedure Set_State (New_State : chars_ptr)"
......@@ -557,9 +621,9 @@ package {process_name} with Elaborate_Body is''']
f' Link_Name => "_set_state";')
dll_api.append(f"{set_state_decl} is")
dll_api.append("begin")
dll_api.append("for S in States loop")
dll_api.append("if To_Upper (Value (New_State))"
" = States'Image (S) then")
dll_api.append(f"for S in {ASN1SCC}{process_name}_States loop")
dll_api.append(f"if To_Upper (Value (New_State))"
f" = {ASN1SCC}{process_name}_States'Image (S) then")
dll_api.append(f"{LPREFIX}.State := S;")
dll_api.append("end if;")
dll_api.append("end loop;")
......@@ -595,33 +659,31 @@ package {process_name} with Elaborate_Body is''']
for substates in aggregates.values():
for each in substates:
process_level_decl.append(
u"function Get_{name}_State return chars_ptr "
u"is (New_String (States'Image ({ctxt}.{name}{sep}state)"
")) with Export, Convention => C, "
'Link_Name => "{proc}_{name}_state";'
.format(name=each.statename, ctxt=LPREFIX,
proc=process_name, sep=SEPARATOR))
f"function Get_{each.statename}_State return chars_ptr "
f"is (New_String ({ASN1SCC}{process_name}_States'Image"
f" ({LPREFIX}.{each.statename}{SEPARATOR}state)"
f")) with Export, Convention => C, "
f'Link_Name => "{process_name}_{each.statename}_state";')
# Functions to get gobal variables (length and value)
for var_name, (var_type, _) in process.variables.items():
# Getters for external applications to view local variables via dll
process_level_decl.append(u"function l_{name}_value"
u" return access {sort} "
u"is ({prefix}.{name}'access) with Export,"
u" Convention => C,"
u' Link_Name => "{name}_value";'
.format(prefix=LPREFIX, name=var_name,
sort=type_name(var_type)))
process_level_decl.append(
f"function l_{var_name}_value"
f" return access {type_name(var_type)} "
f"is ({LPREFIX}.{var_name}'access) with Export,"
f" Convention => C,"
f' Link_Name => "{var_name}_value";')
# Setters for local variables
setter_decl = u"procedure dll_set_l_{name}(value: access {sort})"\
setter_decl = u"procedure DLL_Set_l_{name} (Value: in out {sort})"\
.format(name=var_name, sort=type_name(var_type))
ads_template.append(u'{};'.format(setter_decl))
ads_template.append(u'pragma Export(C, dll_set_l_{name},'
' "_set_{name}");'.format(name=var_name))
dll_api.append(u'{} is'.format(setter_decl))
dll_api.append(u'begin')
dll_api.append(u'{}.{} := value.all;'.format(LPREFIX, var_name))
dll_api.append(u'end dll_set_l_{};'.format(var_name))
ads_template.append(
f'{setter_decl} with Export, Convention => C, '
f'Link_Name => "_set_{var_name}";')
dll_api.append(f'{setter_decl} is')
dll_api.append('begin')
dll_api.append(f'{LPREFIX}.{var_name} := Value;')
dll_api.append(f'end DLL_Set_l_{var_name};')
dll_api.append('')
# Generate the the code of the procedures
......@@ -669,7 +731,7 @@ package {process_name} with Elaborate_Body is''']
# Add (optional) PI parameter (only one is possible in TASTE PI)
if 'type' in signal:
typename = type_name(signal['type'])
pi_header += u'({pName}: access {sort})'.format(
pi_header += u'({pName}: in out {sort})'.format(
pName=param_name, sort=typename)
# Add declaration of the provided interface in the .ads file
......@@ -723,17 +785,13 @@ package {process_name} with Elaborate_Body is''']
# INPUT. The continuous signals are not processed here
if trans and all(each.startswith(trans_st)
for trans_st in trans.possible_states):
taste_template.append(u'p{sep}{ref}{sep}exit;'
.format(ref=each, sep=SEPARATOR))
taste_template.append(f'p{SEPARATOR}{each}{SEPARATOR}exit;')
if input_def:
for inp in input_def.parameters:
# Assign the (optional and unique) parameter
# to the corresponding process variable
taste_template.append(u'{ctxt}.{inp} := {tInp}.all;'
.format(ctxt=LPREFIX,
inp=inp,
tInp=param_name))
taste_template.append(f'{LPREFIX}.{inp} := {param_name};')
# Execute the correponding transition
if input_def.transition:
taste_template.append(u'Execute_Transition ({idx});'
......@@ -754,7 +812,7 @@ package {process_name} with Elaborate_Body is''']
'''
if state.endswith(u'START'):
return