Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
KWH40
fml40-reference-implementation
Commits
170dae2e
Commit
170dae2e
authored
Nov 12, 2020
by
Jiahang Chen
Browse files
add documentation
parent
bd2bee5d
Pipeline
#359800
passed with stages
in 35 seconds
Changes
2
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
ml/dt_factory.py
View file @
170dae2e
...
...
@@ -3,25 +3,6 @@
from
ml.app_logger
import
APP_LOGGER
from
ml.tools
import
remove_namespace
"""
from ml.thing import Thing
from ml.ml40.features.properties.associations.composite import Composite
from ml.fml40.roles.dts.machines.harvester import Harvester
from ml.fml40.features.functionalities.accepts_felling_jobs import AcceptsFellingJobs
from ml.ml40.features.properties.values.rotational_speed import RotationalSpeed
from ml.ml40.roles.dts.parts.engine import Engine
from ml.fml40.features.functionalities.provides_production_data import ProvidesProductionData
from ml.ml40.roles.hmis.machine_ui import MachineUI
from ml.fml40.features.properties.values.documents.jobs.felling_job import FellingJob
from ml.ml40.roles.hmis.hmi import HMI
from ml.fml40.features.properties.values.assortment import Assortment
from ml.fml40.features.properties.values.thickness_class import ThicknessClass
from ml.fml40.features.properties.values.wood_quality import WoodQuality
from ml.fml40.features.properties.values.harvesting_parameter import HarvestingParameters
from ml.fml40.features.properties.values.tree_type import TreeType
from ml.fml40.features.properties.values.harvested_volume import HarvestedVolume
"""
from
ml.thing
import
Thing
from
ml.ml40.roles.servives.service
import
Service
from
ml.ml40.roles.hmis.app
import
App
...
...
@@ -178,8 +159,16 @@
def
build_sub_features
(
feature_ins
,
feature
):
sub_features
=
feature
.
get
(
"subFeatures"
,
[])
"""
build the ml40/fml40 sub features
:param feature_ins: ml40/fml40 feature instance
:type feature_ins: object
:param feature: ml40/fml40 feature
:type feature: dict
"""
sub_features
=
feature
.
get
(
"subFeatures"
,
[])
for
sub_f
in
sub_features
:
sub_f_name
=
sub_f
.
get
(
"class"
,
""
)
sub_f_obj
=
DT_FACTORY
.
get
(
remove_namespace
(
sub_f_name
),
None
)
...
...
@@ -198,8 +187,17 @@ def build_sub_features(feature_ins, feature):
feature_ins
.
subFeatures
[
sub_f_name
]
=
sub_f_instance
def
build_sub_thing
(
feature_ins
,
json_feature
):
json_sub_things
=
json_feature
.
get
(
"targets"
,
[])
def
build_sub_thing
(
feature_ins
,
feature
):
"""
build fml40 sub thing to ml40/fml40 feature
:param feature_ins: ml40/fml40 feature instance
:type feature_ins: object
:param feature: ml40/fml40 feature
:type feature: dict
"""
json_sub_things
=
feature
.
get
(
"targets"
,
[])
for
json_sub_thing
in
json_sub_things
:
sub_thing_ref
=
create_thing
(
model
=
{
"attributes"
:
json_sub_thing
})
sub_thing_name
=
json_sub_thing
.
get
(
"name"
,
None
)
...
...
@@ -207,6 +205,15 @@ def build_sub_thing(feature_ins, json_feature):
def
build
(
thing
,
attributes
):
"""
build ml40 thing
:param thing: ml40 thing instance
:type thing: object
:param attributes: attributes in ml40 thing json
:type attributes: dict
"""
if
not
isinstance
(
attributes
,
dict
):
APP_LOGGER
.
critical
(
"Attributes is no valid json"
)
return
...
...
@@ -222,6 +229,13 @@ def build(thing, attributes):
def
build_role
(
role
):
"""
build ml40/fml40 role in a ml40 thing instance
:param role: ml40/fml40 role
:type role: dict
"""
role_class_name
=
role
.
get
(
"class"
,
""
)
role_obj
=
DT_FACTORY
.
get
(
remove_namespace
(
role_class_name
),
None
)
if
role_obj
is
None
:
...
...
@@ -234,6 +248,13 @@ def build_role(role):
def
build_feature
(
feature
):
"""
build ml40/fml40 feature in a ml40 thing instance
:param feature: ml40/fml40 feature
:type feature: dict
"""
feature_class_name
=
feature
.
get
(
"class"
,
""
)
feature_obj
=
DT_FACTORY
.
get
(
remove_namespace
(
feature_class_name
),
None
)
...
...
@@ -256,6 +277,18 @@ def build_feature(feature):
def
add_function_impl_obj
(
thing
,
impl_obj
,
feature_name
):
"""
add user-specific implemented object to a thing instance
:param thing: ml40 thing instance
:type thing: object
:param impl_obj: ml40/fml40 feature instance
:type impl_obj: object
:param feature_name: class name of a ml40/fml40 feature
:type feature_name: str
"""
feature
=
thing
.
features
.
get
(
feature_name
,
None
)
if
feature
is
None
:
APP_LOGGER
.
critical
(
...
...
@@ -269,6 +302,30 @@ def add_function_impl_obj(thing, impl_obj, feature_name):
def
create_thing
(
model
,
grant_type
=
"password"
,
secret
=
""
,
username
=
None
,
password
=
None
,
is_broker_rest
=
False
,
is_broker
=
False
,
is_repo
=
False
):
"""
create a ml40 thing instance
:param model: JSON of a ml40::thing model
:type model: dict
:param grant_type: grant type of OAuth2.0, which can be password or client_credentials
:type grant_type: str
:param secret: secret of a thing
:type secret: str
:param username: username, if the grant_type is set as password, the username is required
:type username: str
:param password: password, if the grant_type is set as password, the password is required
:type password: str
:param is_broker: whether broker interface is enabled in the ml40::thing instance
:type is_broker: bool
:param is_broker_rest: whether the broker interface uses the HTTP REST
:type is_broker_rest: bool
:param is_repo: whether the repository interface is enabled in the ml40::thing instance
:type is_repo: bool
:returns: ml40::thing instance
:rtype: object
"""
attributes
=
model
.
get
(
"attributes"
,
None
)
if
attributes
is
None
:
...
...
ml/tools.py
View file @
170dae2e
...
...
@@ -30,272 +30,21 @@ class BColors:
UNDERLINE
=
"
\033
[4m"
def
get_idp
(
config
,
grant_type
,
client_id
,
secret
,
username
,
password
):
"""Returns an s3i.IdentiyProvider object
:param config: json object representing a thing configuration
:returns: a s3i.IdentiyProvider object
:rtype:s3i.IdentiyProvider
"""
APP_LOGGER
.
debug
(
"Using grant type %s"
,
grant_type
)
APP_LOGGER
.
debug
(
"Creating identity provider"
)
if
grant_type
==
GRANT_TYPES
[
0
]:
id_p
=
IdentityProvider
(
grant_type
=
"client_credentials"
,
identity_provider_url
=
IDENTITY_PROVIDER_URL
,
realm
=
"KWH"
,
client_id
=
client_id
,
client_secret
=
secret
,
)
else
:
id_p
=
IdentityProvider
(
grant_type
=
grant_type
,
identity_provider_url
=
IDENTITY_PROVIDER_URL
,
realm
=
"KWH"
,
client_id
=
client_id
,
client_secret
=
secret
,
username
=
username
,
password
=
password
,
)
return
id_p
def
get_s3i_broker
(
thing_ref
):
"""Returns a s3i broker for dt_ref.
:param dt_ref: ActorRef of a digital twin
:returns: a s3i broker
:rtype: s3i.Broker
"""
thing_proxy
=
thing_ref
.
proxy
()
access_token
=
thing_proxy
.
name
.
get
()
s3i_broker
=
Broker
(
auth_form
=
"Username/Password"
,
username
=
""
,
password
=
access_token
,
host
=
"rabbitmq.s3i.vswf.dev"
,
)
return
s3i_broker
def
get_receiver_callback_func
(
dt_ref
):
"""Returns a function that calls tell() on dt_ref. All arguments
passed to the returned function are bundled before calling tell().
:param dt_ref: ActorRef of a digital twin
:returns: function
"""
def
callback
(
dt_ref
,
channel
,
method
,
properties
,
body
):
"""Handler for receiving messages.
:param ch:
:param method:
:param properties:
:param body: received message
"""
# body["ch"] = channel
# body["method"] = method
# body["properties"] = properties
print
(
"test"
)
dt_ref
.
tell
(
body
)
print
(
"test1"
)
tmp
=
partial
(
callback
,
dt_ref
)
return
tmp
def
print_ts
(
msg
,
end
=
"
\n
"
):
"""
Prints the given message in the s3i style
:param msg: message to print
:type msg: string
"""
global
console_no_new_line
trail
=
""
if
end
==
"
\r
"
:
console_no_new_line
=
True
elif
console_no_new_line
:
console_no_new_line
=
False
trail
=
"
\n
"
t_stamp
=
datetime
.
datetime
.
fromtimestamp
(
time
.
time
()).
strftime
(
"%H:%M:%S.%f"
)[:
-
3
]
prompt_msg
=
"{}[S³I][{}] {}{}"
.
format
(
trail
,
t_stamp
,
msg
,
end
)
print
(
prompt_msg
)
def
input_ts
(
msg
):
"""Asks for user input and returns it."""
t_stamp
=
datetime
.
datetime
.
fromtimestamp
(
time
.
time
()).
strftime
(
"%H:%M:%S.%f"
)[:
-
3
]
prompt_msg
=
"[S³I][{}] {}"
.
format
(
t_stamp
,
msg
)
return
input
(
prompt_msg
)
def
send_requests
(
dt_ref
,
requests
):
"""Sends all given requests.
:param access_token: valid access token
:param sender_id: id of the sender
:param requests: iterable of tuples(receiver_ids,
service_type). Where receiver_ids represents an iterable object of
ids.
"""
dt_proxy
=
dt_ref
.
proxy
()
access_token
=
dt_proxy
.
access_token
.
get
()
sender_id
=
dt_proxy
.
thing_id
.
get
()
for
request
in
requests
:
send_request
(
access_token
,
sender_id
,
request
[
1
],
request
[
0
],
request
[
2
])
def
send_request
(
access_token
,
sender_id
,
receiver_ids
,
service_type
,
parameters
=
{}):
"""Sends a message of type service_type from sender_id to receiver_id
with the given content specified by parameters.
:param access_token: valid access token
:param sender_id: id of the sender
:param receiver_ids:
:param service_type:
:param parameters:
:returns:
:rtype:
"""
receiver_endpoints
=
[(
"s3ib://{}"
.
format
(
i
))
for
i
in
receiver_ids
]
s3i_broker
=
Broker
(
auth_form
=
"Username/Password"
,
username
=
" "
,
password
=
access_token
,
host
=
"rabbitmq.s3i.vswf.dev"
,
)
req
=
create_request
(
sender_id
,
receiver_ids
,
service_type
,
parameters
)
msg_str
=
req
.
msg
.
__str__
()
s3i_broker
.
send
(
receiver_endpoints
,
req
.
msg
.
__str__
())
APP_LOGGER
.
debug
(
f
"Message send:
{
msg_str
}
"
)
def
send_message
(
broker_obj
,
dir_obj
,
msg
:
dict
):
if
isinstance
(
msg
,
dict
):
receivers
=
msg
.
get
(
"receivers"
,
None
)
for
r
in
receivers
:
broker_obj
.
send
([
find_broker_endpoint
(
dir_obj
,
thing_id
=
r
)],
json
.
dumps
(
msg
))
def
find_broker_endpoint
(
dir_obj
,
thing_id
):
thing_json
=
dir_obj
.
queryThingIDBased
(
thing_id
)
all_endpoints
=
thing_json
[
"attributes"
].
get
(
"allEndpoints"
,
None
)
if
all_endpoints
:
for
ep
in
all_endpoints
:
if
"s3ib"
in
ep
:
return
ep
def
verify_message
(
message
,
message_type
,
service_type
):
# TODO: Exception handling
if
message
[
"messageType"
]
!=
message_type
:
return
False
if
message
[
"serviceType"
]
!=
service_type
:
return
False
return
True
def
decode_message
(
msg
,
decode
=
True
):
# convert bytes to str
tmp
=
msg
if
decode
:
tmp
=
msg
.
decode
(
"utf8"
)
body_str
=
tmp
.
replace
(
"'"
,
'"'
).
replace
(
"True"
,
"true"
).
replace
(
"False"
,
"false"
)
return
body_str
def
get_end_point
(
access_token
,
thing_id
):
s3i_directory
=
Directory
(
s3i_dir_url
=
"https://dir.s3i.vswf.dev/api/2/"
,
token
=
access_token
)
# TDOO: How can this throw?
try
:
return
s3i_directory
.
queryAttributeBased
(
"thingId"
,
thing_id
)
except
Exception
:
return
None
def
create_request
(
sender_id
,
receiver_ids
,
service_type
,
parameters
=
{}):
"""Returns a service request"""
msg_uuid
=
"s3i:"
+
str
(
uuid
.
uuid4
())
sender_uuid
=
id_to_endpoint
(
sender_id
)
req
=
ServiceRequest
()
req
.
fillServiceRequest
(
sender_id
,
receiver_ids
,
sender_uuid
,
service_type
,
parameters
,
msgUUID
=
msg_uuid
)
return
req
# TODO: Pass access token instead of idp
def
dir_search_with_name
(
idp
,
name
):
"""Queries the s3i directory for the id corresponding to name and
returns it.
:param idp: s3i.ServiceProvider object
:param name: name of the thing
:returns: id of the thing
:rtype: str
def
make_sub_thing
(
name
,
roles
,
features
=
[]):
"""
create a JSON for a fml40 sub thing.
s3i_directory
=
Directory
(
s3i_dir_url
=
"https://dir.s3i.vswf.dev/api/2/"
,
token
=
idp
.
get_token
(
TokenType
.
ACCESS_TOKEN
),
)
# TDOO: How can this throw?
try
:
return
s3i_directory
.
queryAttributeBased
(
"name"
,
name
)[
0
][
"thingId"
]
except
Exception
:
return
None
:param name: name of the sub thing
:type name: str
:param roles: fml40 roles of the sub thing
:type roles: str
:param features: fml40 features of the sub thing
:type features: list
def
dir_id_to_defualt_endpoints
(
idp
,
thing_id
):
"""Queries the s3i directory for the endpoint of the thing identifyied
by id and returns it.
:param idp: s3i.ServiceProvider object
:param id: id of the thing
:returns: endpoint
:rtype: str
:returns: JSON of the sub thing
:rtype: dict
"""
s3i_directory
=
Directory
(
s3i_dir_url
=
"https://dir.s3i.vswf.dev/api/2/"
,
token
=
idp
.
get_token
(
TokenType
.
ACCESS_TOKEN
),
)
try
:
endpoint
=
s3i_directory
.
queryThingIDBased
(
thing_id
+
"/attributes/defaultEndpoints"
)
except
Exception
:
endpoint
=
[]
return
endpoint
def
dir_name_to_default_endpoints
(
idp
,
name
):
"""Queries the s3i directory for the endpoint of the thing identifyied
by name.
:param idp: s3i.ServiceProvider object
:param name: name of the thing
:returns: endpoint
:rtype: str
"""
thing_id
=
dir_search_with_name
(
idp
,
name
)
endpoint
=
dir_id_to_defualt_endpoints
(
idp
,
thing_id
)
return
endpoint
def
make_sub_thing
(
name
,
roles
,
features
=
[]):
sub_thing
=
{
"class"
:
"ml40::Thing"
,
"name"
:
name
,
...
...
@@ -307,6 +56,22 @@ def make_sub_thing(name, roles, features=[]):
def
make_feature_config
(
class_name
,
identifier
=
""
,
name
=
""
,
subFeatures
=
""
):
"""
Create a JSON for a fml40 feature
:param class_name: class name of the fml40 feature
:type class_name: str
:param identifier: local id of the fml40 feature
:type identifier: str
:param name: name of the fml40 feature
:type name: str
:param subFeatures: sub features
:type subFeatures: dict
:returns: JSON of the fml40 feature
:rtype: dict
"""
config_json
=
{
"class"
:
class_name
}
...
...
@@ -321,6 +86,25 @@ def make_feature_config(class_name, identifier="", name="", subFeatures=""):
def
make_thing_config
(
thing_id
,
name
,
roles
,
features
=
[],
config_path
=
""
):
"""
Create a configuration file (JSON) for a fml40 thing.
:param thing_id: identifier of the thing
:type thing_id: str
:param name: name of the thing
:type name: str
:param roles: fml40 roles of the thing
:type roles: list
:param features: fml40 features of the thing
:type features: list
:param config_path: path where the configuration file should be located
:type config_path: str
:returns: name of the configuration file of the thing
:rtype: str
"""
if
not
config_path
:
config_path
=
os
.
path
.
join
(
"__file__"
,
"configs"
)
...
...
@@ -341,9 +125,10 @@ def make_thing_config(thing_id, name, roles, features=[], config_path=""):
def
load_config
(
config_filepath
):
"""
Creates
a json object from a json formatted file found at config_filepath.
"""
load
a json object from a json formatted file found at config_filepath.
:param config_filepath: Path to json formatted file.
:type config_filepath: str
"""
with
open
(
config_filepath
)
as
config_file
:
...
...
@@ -351,52 +136,31 @@ def load_config(config_filepath):
return
config
def
load_configs
(
config_filepaths
):
"""Creates json objects from json formatted files specified by the
entries of config_filepaths.
:param config_filepath: Iterable of paths to json formatted files.
def
find_broker_endpoint
(
dir_obj
,
thing_id
):
"""
res
=
list
(
load_config
(
config
)
for
config
in
config_filepaths
)
return
res
find the S3I-B endpoint of a thing
def
id_to_endpoint
(
thing_id
):
"""Returns the endpoint for thing_id.
:param thing_id: id
:returns: endpoint
:rtype: str
:param dir_obj: S³I Directory Object
:type dir_obj: object
:param thing_id: identifier of the searched thing
"""
thing_json
=
dir_obj
.
queryThingIDBased
(
thing_id
)
all_endpoints
=
thing_json
[
"attributes"
].
get
(
"allEndpoints"
,
None
)
if
all_endpoints
:
for
ep
in
all_endpoints
:
if
"s3ib"
in
ep
:
return
ep
if
thing_id
.
startswith
(
"s3ib//"
):
return
id
return
"s3ib://{}"
.
format
(
thing_id
)
def
get_requests
(
config
):
"""Parses config and returns a list of requests.
Requests are tuples (service_type, receiver_uuid, parameters)
:param config: json object representing the configuration of a
thing
:returns: list of requests
:rtype: list(tuple(service_type, receiver_uuid, parameters))
def
remove_namespace
(
input_str
):
"""
requests
=
[]
for
txt
in
config
[
"requests"
]:
msg
=
config
[
"requests"
][
txt
]
msg_type
=
msg
[
"msg_type"
]
parameters
=
msg
.
get
(
"parameters"
,
{})
receiver_uuids
=
[]
for
target
in
msg
[
"receivers"
]:
receiver_uuids
.
append
(
target
)
requests
.
append
((
msg_type
,
receiver_uuids
,
parameters
))
return
requests
remove the namespace like ml40 or fml40
:param input_str: input with namespace
:type input_str: str
:returns: output without namespace
:rtype: str
def
remove_namespace
(
input_str
):
"""
return
input_str
.
replace
(
"fml40::"
,
""
).
replace
(
"ml40::"
,
""
)
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment