Skip to content
Snippets Groups Projects
Commit f00141e6 authored by GromeTT's avatar GromeTT
Browse files

Merge branch 'feature_documentation' of...

Merge branch 'feature_documentation' of https://git.rwth-aachen.de/kwh40/fml40-reference-implementation into feature_documentation_alb
parents 60a0b117 45cfa7ff
No related branches found
No related tags found
3 merge requests!8Feature documentation,!6Feature documentation alb,!5Feature documentation alb
import s3i
import jwt
from ml.tools import load_config, make_thing_config, make_sub_thing
from ml.dt_factory import create_thing, build_feature, add_function_impl_obj
from ml.fml40.features.functionalities.accepts_felling_jobs import AcceptsFellingJobs
from ml.ml40.features.properties.values.documents.jobs.job_status import JobStatus
from ml.app_logger import APP_LOGGER, setup_logger
import time
from config import *
import os
import json
from config import *
dt_creation_app_id = dt_creation_app_id
dt_creation_app_secret = dt_creation_app_secret
# username = input('[S3I]: Please enter your username:').strip('," ')
# password = input('[S3I]: Please enter your password:')
cred = chen
username = cred["username"]
password = cred["password"]
s3i_identity_provider = s3i.IdentityProvider(grant_type='password',
identity_provider_url="https://idp.s3i.vswf.dev/",
realm='KWH',
client_id=dt_creation_app_id,
client_secret=dt_creation_app_secret,
username=username,
password=password)
access_token = s3i_identity_provider.get_token(s3i.TokenType.ACCESS_TOKEN)
''' decode the access token'''
parsed_username = jwt.decode(access_token, verify=False)["preferred_username"]
### create identity of digital twin
"""
s3i_config = s3i.Config(access_token)
resp = s3i_config.create_thing()
dt_id = resp.json().get("identifier", None)
dt_secret = resp.json().get("secret", None)
s3i_config.create_broker_queue(thing_id=dt_id)
res = s3i_config.create_cloud_copy(thing_id=dt_id)
"""
dt_cred = {
"identifier": dt_id,
"secret": dt_secret
}
dt_name = "my_dt_harvester"
config_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, "configs"))
cred_filepath = os.path.join(config_path, "{}_cred.json".format(dt_name))
with open(cred_filepath, 'wb') as file:
file.write(json.dumps(dt_cred).encode('utf-8'))
config_engine = make_sub_thing(name="my_engine", roles=[{"class": "ml40::Engine"}],
features=[
{"class": "ml40::RotationalSpeed",
"rpm": 2001}
])
config_cran = make_sub_thing(name="my_bord_computer", roles=[{"class": "ml40::MachineUI"}])
config_file_name = make_thing_config(thing_id=dt_id, name=dt_name, roles=[{"class": "fml40::Harvester"}],
features=[{"class": "fml40::ProvidesProductionData"},
{"class": "fml40::AcceptsFellingJobs"},
{"class": "ml40::Location", "longitude": 6.45435, "latitude": 52.543534},
{"class": "ml40::Composite",
"targets": [config_engine, config_cran]}],
config_path=config_path)
setup_logger(dt_name)
dt_model = load_config(config_filepath=os.path.join(config_path, "my_dt_harvester.json"))
with open(cred_filepath) as file:
dt_cred = json.load(file)
dt = create_thing(model=dt_model, grant_type="client_credentials", secret=dt_cred.get("secret"),
is_broker_rest=True,
is_broker=True, is_repo=True)
class AcceptsFellingJobsImpl(AcceptsFellingJobs):
def __init__(self, name="", identifier=""):
super(AcceptsFellingJobs, self).__init__(
name=name,
identifier=identifier)
self.job_list = []
def acceptJob(self, job):
APP_LOGGER.info("Checking if the felling job can be accepted.")
if isinstance(job, dict):
try:
felling_job = build_feature(feature=job)
for job in self.job_list:
if job.identifier == felling_job.identifier:
APP_LOGGER.info("Job with ID {} has been rejected, because this job was already accepted".format(
felling_job.identifier))
return False
felling_job.status = JobStatus.InProgress.name
self.job_list.append(felling_job)
APP_LOGGER.info("Job with ID {} has been accepted".format(felling_job.identifier))
return True
except:
APP_LOGGER.info("Job with ID {} has been rejected".format(felling_job.identifier))
return False
def queryJobStatus(self, identifier):
APP_LOGGER.info("Checking the job status of job {}".format(identifier))
for job in self.job_list:
if job.identifier == identifier:
APP_LOGGER.info("Job {} is now in status {}".format(identifier, job.status))
return {"identifier": identifier, "status": job.status}
APP_LOGGER.info("Job {} can not be queried".format(identifier))
return {"identifier": identifier, "status": "NOT FOUND"}
def removeJob(self, identifier):
APP_LOGGER.info("Checking if i can remove the job {}".format(identifier))
for job in self.job_list:
if job.identifier == identifier:
self.job_list.remove(job)
APP_LOGGER.info("Job {} removed".format(identifier))
return True
APP_LOGGER.info("Job {} can not be found".format(identifier))
return False
def simulate_rpm():
my_engine = dt.features["ml40::Composite"].targets["my_engine"]
tank = "up"
while True:
if tank == "down":
__new_rpm = my_engine.features["ml40::RotationalSpeed"].rpm - 10
if __new_rpm < 2000:
tank = "up"
elif tank == "up":
__new_rpm = my_engine.features["ml40::RotationalSpeed"].rpm + 10
if __new_rpm > 2500:
tank = "down"
my_engine.features["ml40::RotationalSpeed"].rpm = __new_rpm
time.sleep(1)
add_function_impl_obj(dt, AcceptsFellingJobsImpl, "fml40::AcceptsFellingJobs")
dt.add_user_def(func=simulate_rpm)
dt.run_forever()
import s3i
import jwt
import uuid
from ml.tools import find_broker_endpoint, make_thing_config, load_config, make_feature_config
from ml.dt_factory import create_thing, build_feature
from ml.app_logger import setup_logger
import requests
import json
#from config import *
import os
from config import *
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
config_path = os.path.join(root_path, "configs")
cred = chen
hmi_id = cred["application_id"]
hmi_secret = cred["application_secret"]
username = cred["username"]
password = cred["password"]
setup_logger("my HMI")
config_file_name = make_thing_config(thing_id=hmi_id, name="my_HMI", roles=[{"class": "ml40::HMI"}],
config_path=config_path)
hmi_model = load_config(config_filepath=os.path.join(config_path, config_file_name))
hmi = create_thing(model=hmi_model, grant_type="password", secret=hmi_secret,
username=username, password=password,
is_broker_rest=True, is_broker=True, is_repo=False)
hmi.run_forever()
hmi_endpoint = find_broker_endpoint(hmi.dir, hmi_id)
receiver = input("[S³I]: Please enter the id of your digital twin: ")
"""
Print out thing entry from directory and repository
"""
serv_req = s3i.messages.ServiceRequest()
subFeatures = [{
"class": "fml40::Assortment",
"grade": "fl",
"name": "Stammholz Abschnitte",
"subFeatures": [
{
"class": "fml40::ThicknessClass",
"name": ">"
},
{
"class": "fml40::WoodQuality",
"name": "B-C"
},
{
"class": "fml40::HarvestingParameters",
"cuttingLengths": 20
},
{
"class": "fml40::TreeType",
"name": "Spruce",
"conifer": True
},
{
"class": "fml40::HarvestedVolume",
"volume": 140
}
]
}]
feature_config_json = make_feature_config(class_name="fml40::FellingJob", subFeatures=subFeatures,
name="my_felling_job")
felling_job = build_feature(feature=feature_config_json)
serv_req.fillServiceRequest(
senderUUID=hmi_id, receiverUUID=[receiver], sender_endpoint=hmi_endpoint,
#serviceType="fml40::AcceptsFellingJobs/removeJob",
serviceType="fml40::AcceptsFellingJobs/acceptJob",
#parameters={"identifier": "s3i:d09214a7-46bb-4672-b4e0-2c0aa0e395a3"},
parameters={"job": felling_job.to_json()},
msgUUID="s3i:{}".format(uuid.uuid4())
)
getv_req = s3i.GetValueRequest()
rpm_path = "attributes/features/ml40::Composite/targets/ml40::Engine/features/ml40::RotationalSpeed/rpm"
#rpm_path = "attributes/name"
#rpm_path = ""
getv_req.fillGetValueRequest(
senderUUID=hmi_id, receiverUUID=[receiver], sender_endpoint=hmi_endpoint,
attributePath=rpm_path, msgUUID="s3i:{}".format(uuid.uuid4())
)
receiver_endpoint = find_broker_endpoint(hmi.dir, thing_id=receiver)
resp = hmi.broker.send([receiver_endpoint], json.dumps(serv_req.msg))
print(resp.text)
\ No newline at end of file
import ml
import os
import json
import s3i
import uuid
"""
Configure the HMI.
The credentials file named hmi_cred.json must be located in the folder configs.
The configuration file named hmi.json must also be located in the same folder
"""
ml.setup_logger("hmi")
config_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "configs"))
cred_path = os.path.join(config_path, "hmi_cred.json")
with open(cred_path) as file:
hmi_cred = json.load(file)
class bcolors:
"""colors for the console log"""
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def prepare_service_request():
"""
Prepare a S³I-B Service Request
:return: S³I-B Service Request
:rtype: dict
"""
available_services = ["fml40::AcceptsFellingJobs", "fml40::ProvidesProductionData"]
print("[S³I]: Following services are available: {0}{1}{2}".format(bcolors.UNDERLINE, available_services,
bcolors.ENDC))
class_name = input("[S³I]: Please enter one of these services: ")
serv_req = s3i.messages.ServiceRequest()
while class_name not in available_services:
print("[S³I]: service {0}{1}{2} is not available".format(bcolors.FAIL, class_name, bcolors.ENDC))
print("[S³I]: Following services are available: {0}{1}{2}".format(bcolors.UNDERLINE, available_services,
bcolors.ENDC))
class_name = input("[S³I]: Please enter one of these services: ")
if class_name == "fml40::AcceptsFellingJobs":
available_methods = ["acceptJob", "queryJobStatus", "removeJob"]
method = input("[S³I]: which method should be called? {0}{1}{2} ".format(bcolors.UNDERLINE, available_methods,
bcolors.ENDC))
while method not in available_methods:
method = input(
"[S³I]: which method should be called? {0}{1}{2} ".format(bcolors.UNDERLINE, available_methods,
bcolors.ENDC))
if method == "acceptJob":
subFeatures = [{
"class": "fml40::Assortment",
"grade": "fl",
"name": "Stammholz Abschnitte",
"subFeatures": [
{
"class": "fml40::ThicknessClass",
"name": ">"
},
{
"class": "fml40::WoodQuality",
"name": "B-C"
},
{
"class": "fml40::HarvestingParameters",
"cuttingLengths": 20
},
{
"class": "fml40::TreeType",
"name": "Spruce",
"conifer": True
},
{
"class": "fml40::HarvestedVolume",
"volume": 140
}
]
}]
feature_config_json = ml.make_feature_config(class_name="fml40::FellingJob", subFeatures=subFeatures,
name="my_felling_job")
felling_job = ml.build_feature(feature=feature_config_json)
felling_job_json = felling_job.to_json()
print("[S³I]: A felling job is generated with the job id: {}".format(felling_job_json["identifier"]))
parameter = {"job": felling_job_json}
elif method == "queryJobStatus":
job_id = input("[S³I]: Please enter the job id: ")
parameter = {"identifier": job_id}
elif method == "removeJob":
job_id = input("[S³I]: Please enter the job id: ")
parameter = {"identifier": job_id}
elif class_name == "fml40::ProvidesProductionData":
available_methods = ["getProductionData"]
method = input("[S³I]: which method should be called? {0}{1}{2} ".format(bcolors.UNDERLINE, available_methods,
bcolors.ENDC))
while method not in available_methods:
method = input(
"[S³I]: which method should be called? {0}{1}{2} ".format(bcolors.UNDERLINE, available_methods,
bcolors.ENDC))
if method == "getProductionData":
name = input("[S³I]: Please enter the name of your production data: [Stammsegment 4711, Stammsegment 4712] ")
parameter = {"name": name}
serv_req.fillServiceRequest(
senderUUID=hmi_cred.get("identifier"), receiverUUID=[receiver], sender_endpoint=hmi_endpoint,
serviceType="{}/{}".format(class_name, method),
parameters=parameter,
msgUUID="s3i:{}".format(uuid.uuid4())
)
return serv_req.msg
def prepare_get_value_request():
"""
Prepare a S³I-B GetValueRequest
:return: S³I-B GetValueRequest
:rtype: dict
"""
getv_req = s3i.GetValueRequest()
attribute_path = input("[S³I]: Please enter the attribute path: ["
"attributes/features/ml40::Composite/targets/ml40::Engine/features/ml40::RotationalSpeed"
"/rpm]")
getv_req.fillGetValueRequest(
senderUUID=hmi_cred.get("identifier"), receiverUUID=[receiver], sender_endpoint=hmi_endpoint,
attributePath=attribute_path, msgUUID="s3i:{}".format(uuid.uuid4())
)
return getv_req.msg
if __name__ == "__main__":
"""
Configure and run the HMI
"""
config_file_name = ml.make_thing_config(thing_id=hmi_cred.get("identifier"), name="hmi",
roles=[{"class": "ml40::HMI"}],
config_path=config_path)
hmi_model = ml.load_config(config_filepath=os.path.join(config_path, config_file_name))
hmi = ml.create_thing(model=hmi_model, grant_type="password", secret=hmi_cred.get("secret"),
username=hmi_cred.get("username"), password=hmi_cred.get("password"),
is_broker_rest=False, is_broker=True, is_repo=False)
hmi.run_forever()
hmi_endpoint = ml.find_broker_endpoint(hmi.dir, hmi_cred.get("identifier"))
receiver = "s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03"
"""
While loop to edit S³I messages and then send it to the Digital Twin
"""
while True:
print("[S³I]: You can send following messages to forwarder a by entering 1 or 2")
request_type = input(" {0}[1]: service request, [2]: get value request:{1} ".format(
bcolors.OKBLUE, bcolors.ENDC
))
if request_type == "1":
msg = prepare_service_request()
if msg is None:
continue
elif request_type == "2":
msg = prepare_get_value_request()
if msg is None:
continue
else:
continue
receiver_endpoint = ml.find_broker_endpoint(hmi.dir, thing_id=receiver)
resp = hmi.broker.send([receiver_endpoint], json.dumps(msg))
......@@ -6,6 +6,7 @@
Welcome to the documentation of the fml40 python reference implementation!
==========================================================================
The documentation is composed of some preliminaries for installation, a quick start and the reference implementation package.
We have implemented a permanent Digital Twin which runs in our internal server. In order to communicate with it, you can use the `hmi.py` in the folder `demo`
Table of Contents
-----------------
......@@ -14,12 +15,14 @@ Table of Contents
:caption: Contents:
md/preliminaries.md
md/quick_start.md
thing
feature
identifier
role
dt_factory
tools
md/permanent_dt.md
Indices and tables
==================
......
# Permanent Digital Twin
With the fml40-python-lib, we have developed a Digital Twin, which is modeled based on the language FML40 and runs permanent. The Digital Twin is implemented using fog approach, which means it is composed of the part of edge DT, which lives in our internal server, and the part of cloud DT, which runs at the S³I Repository. The S³I Interface is enabled in the DT. To communicate with the DT, we provided a python script named `hmi.py` in the folder `demo`. Please feel free to contact with us in order to get the credentials file for the HMI and then just run the hmi.py to communicate with the DT after installing the corresponding python virtual environment. This documentation introduces the main features of the Digital Twin and JSON entries in the S³I Directory and Repository.
## S³I Directory entry
S³I Directory entry describes a thing at the layer of the metadata, which can be mapped to a JSON file. In order to query the JSON file, you can send a HTTP Request to the S³I Directory. Regarding how to send a HTTP Request, refer to [the S³I Document](https://kwh40.pages.rwth-aachen.de/-/s3i/-/jobs/1095388/artifacts/public/html/md/communication.html). The Digital Twin has a directory entry shown below:
```
{
"thingId": "s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03",
"policyId": "s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03",
"attributes": {
"owner": "318b5bc4-7479-4d8d-b347-1019ca194d81",
"class": "ml40::Thing",
"allEndpoints": [
"s3ib://s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03",
"https://ditto.s3i.vswf.dev/api/2/things/s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03/"
],
"type": "component",
"name": "mmi_fog_harvester",
"location": {
"longitude": 6.45435,
"latitude": 52.543534
},
"dataModel": "fml40",
"thingStructure": {
"class": "ml40::Thing",
"links": [
{
"association": "roles",
"target": {
"class": "fml40::Harvester",
"identifier": "s3i:e1b8b3d6-65a6-4733-876a-dbde9ff72afd"
}
},
{
"association": "features",
"target": {
"class": "fml40::ProvidesProductionData",
"identifier": "s3i:3cece6c6-5e94-448d-b95d-9e3b3c02e6da"
}
},
{
"association": "features",
"target": {
"class": "ml40::ProductionData",
"identifier": "s3i:30b9325f-ce4f-4c55-89f7-663eb4b76273"
}
},
{
"association": "features",
"target": {
"class": "fml40::AcceptsFellingJobs",
"identifier": "s3i:2ce793d4-0b6f-472e-abcb-c0e1e9b084af"
}
},
{
"association": "features",
"target": {
"class": "fml40::Harvests",
"identifier": "s3i:94f8c4d1-9d6c-45e8-a137-3bb33b1004b6"
}
},
{
"association": "features",
"target": {
"class": "ml40::JobList",
"identifier": "s3i:3ba16ab9-a472-42b7-af97-c19acef55196"
}
},
{
"association": "features",
"target": {
"class": "ml40::Composite",
"identifier": "s3i:316f0140-8ce2-440b-badd-3ece563c167c",
"links": [
{
"class": "ml40::Thing",
"identifier": "s3i:a57543fb-2b03-43d8-ac4b-f3ba24a545c4",
"links": [
{
"association": "roles",
"target": {
"class": "ml40::Engine",
"identifier": "s3i:2fb281c8-180f-41fb-a333-7b83d83cfae0"
}
},
{
"association": "features",
"target": {
"class": "ml40::RotationalSpeed",
"identifier": "s3i:209eb6e3-4a16-458c-952b-dcf3569d062b"
}
}
]
},
{
"class": "ml40::Thing",
"identifier": "s3i:97453feb-e15d-463c-9a45-ec4c59b4c8c8",
"links": [
{
"association": "roles",
"target": {
"class": "ml40::Crane",
"identifier": "s3i:8f480a94-7b0a-4293-96a9-08ddb0360af6"
}
},
{
"association": "features",
"target": {
"class": "ml40::Shared",
"identifier": "s3i:5cba3433-051b-4e22-9245-30e4c6a45200"
}
}
]
}
]
}
}
]
}
}
}
```
## S³I Repository entry
In a run time environment like edge device or S³I Repository, a conceptual fml40 data model can be directly mapped to a JSON file which describes the thing itself. The Repository entry can be queried via HTTP REST Request, refer to [S³I Documentation](https://kwh40.pages.rwth-aachen.de/-/s3i/-/jobs/1095388/artifacts/public/html/md/communication.html). The permanent Digital Twin has a repository entry which is shown in the following:
```
{
"thingId": "s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03",
"policyId": "s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03",
"attributes": {
"class": "ml40::Thing",
"name": "mmi_fog_harvester",
"roles": [
{
"class": "fml40::Harvester",
"identifier": "s3i:e1b8b3d6-65a6-4733-876a-dbde9ff72afd"
}
],
"features": [
{
"class": "fml40::ProvidesProductionData",
"identifier": "s3i:3cece6c6-5e94-448d-b95d-9e3b3c02e6da"
},
{
"class": "ml40::ProductionData",
"identifier": "s3i:30b9325f-ce4f-4c55-89f7-663eb4b76273",
"subFeatures": [
{
"class": "ml40::Composite",
"identifier": "s3i:78309618-f796-4132-92c9-a3e923506463",
"targets": [
{
"class": "ml40::Thing",
"identifier": "s3i:70e808e0-0e0c-4bca-bbaf-23a350c3cf91",
"name": "Stammsegment 4711",
"roles": [
{
"class": "fml40::StemSegment",
"identifier": "s3i:1cbe9e8d-cc80-47fb-baab-20878043348a"
}
],
"features": [
{
"class": "ml40::Location",
"identifier": "s3i:6cdbd9a5-95e2-4539-bd5d-c5238a9d952f",
"latitude": 50.1231,
"longitude": 6.1231
},
{
"class": "fml40::StemSegmentProperties",
"identifier": "s3i:ddeb1e34-1a74-4f6e-a806-75d1cfe98d68",
"diameter": 0.4,
"length": 2,
"woodType": "Spruce"
}
]
},
{
"class": "ml40::Thing",
"identifier": "s3i:55d6793c-37e3-4433-a214-89513bd1cfe0",
"name": "Stammsegment 4712",
"roles": [
{
"class": "fml40::StemSegment",
"identifier": "s3i:0c24170c-e122-454d-b803-a94af462c554"
}
],
"features": [
{
"class": "ml40::Location",
"identifier": "s3i:d1b80ca5-df18-4ed9-b9a9-129e18cb8b76",
"latitude": 50.1231,
"longitude": 6.1231
},
{
"class": "fml40::StemSegmentProperties",
"identifier": "s3i:57d09fc1-4218-4fc6-9e80-c6d2fc44c2f4",
"diameter": 0.6,
"length": 3,
"woodType": "Spruce"
}
]
}
]
}
]
},
{
"class": "fml40::AcceptsFellingJobs",
"identifier": "s3i:2ce793d4-0b6f-472e-abcb-c0e1e9b084af"
},
{
"class": "fml40::Harvests",
"identifier": "s3i:94f8c4d1-9d6c-45e8-a137-3bb33b1004b6"
},
{
"class": "ml40::JobList",
"identifier": "s3i:3ba16ab9-a472-42b7-af97-c19acef55196"
},
{
"class": "ml40::Composite",
"identifier": "s3i:316f0140-8ce2-440b-badd-3ece563c167c",
"targets": [
{
"class": "ml40::Thing",
"identifier": "s3i:a57543fb-2b03-43d8-ac4b-f3ba24a545c4",
"name": "Motor",
"roles": [
{
"class": "ml40::Engine",
"identifier": "s3i:2fb281c8-180f-41fb-a333-7b83d83cfae0"
}
],
"features": [
{
"class": "ml40::RotationalSpeed",
"identifier": "s3i:209eb6e3-4a16-458c-952b-dcf3569d062b",
"rpm": 2331
}
]
},
{
"class": "ml40::Thing",
"identifier": "s3i:97453feb-e15d-463c-9a45-ec4c59b4c8c8",
"name": "Kran",
"roles": [
{
"class": "ml40::Crane",
"identifier": "s3i:8f480a94-7b0a-4293-96a9-08ddb0360af6"
}
],
"features": [
{
"class": "ml40::Shared",
"identifier": "s3i:5cba3433-051b-4e22-9245-30e4c6a45200",
"targets": [
{
"class": "ml40::Thing",
"identifier": "s3i:efbc9eef-82a1-4ed1-86d0-b06170cae3cd",
"name": "Harvesterkopf",
"roles": [
{
"class": "fml40::HarvestingHead",
"identifier": "s3i:f338cd4d-ca21-46db-8b3b-a17c7c026b6d"
}
],
"features": [
{
"class": "fml40::Cuts",
"identifier": "s3i:83d8e549-0d07-49f4-9906-6a729b3e6e9b"
},
{
"class": "fml40::Grabs",
"identifier": "s3i:cf6ad395-69d2-493f-838e-82bbc74c4273"
}
]
}
]
}
]
}
]
}
]
},
"features": {}
}
```
## Service Call
As shown in the directory entry, the DT provided diverse service functions. A list containing all embedded service functions can be found below:
```
class: fml40::AcceptsFellingJobs
methods:
- acceptJob(job)
- queryJobStatus(identifier)
- removeJob(identifier)
class: fml40::ProvidesProductionData
methods:
- getProductionData(name)
```
In order to call those functions remotely, we need to send a S³I-B ServiceRequest to the Digital Twin via the S³I Broker. The class/method name and parameters need to be declared within the message.
The following example shows how to call `removeJob` of the class `fml40::AcceptsFellingJob`
```
{
"identifier": "s3i:94f8c4d1-9d6c-45e8-a137-3bb33b1004b6" # message ID
"sender": "s3i:7f46a255-3245-439e-84f0-090f0b221965", # ID of my private HMI
"receivers": ["s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03"], # ID of the Digital Twin
"serviceType": "fml40::AcceptsFellingJob/removeJob", # specifies the method and class, which should be called
"parameters": {"identifier": "s3i:3ba16ab9-a472-42b7-af97-c19acef55196"}, # specifies the input
"messageType": "ServiceRequest", # message type
"replyToEndpoint": "s3ib://s3i:7f46a255-3245-439e-84f0-090f0b221965" # Endpoint of my private HMI,
}
```
The message can be sent directly via [the S³I Broker API](https://broker.s3i.vswf.dev/apidoc) or using [S³I Python Lib](https://kwh40.pages.rwth-aachen.de/-/s3i/-/jobs/1095388/artifacts/public/html/S3I-Package.html), An example can be found [here](https://git.rwth-aachen.de/kwh40/s3i/-/blob/master/demo/demo2_sender_hmi.py)
## Query Value
A service is implemented in the DT with the purpose of handling S³I GetValueRequest, so we can send a getValueRequest message to query a specified value of the original JSON model in the edge DT. In the following, we provide also an example to query the current motor rpm value.
```
{
'sender': 's3i:7f46a255-3245-439e-84f0-090f0b221965', # ID of my private HMI
'identifier': 's3i:23c99920-c369-4074-83b9-17beb84bb2d2', # message ID
'receivers': ['s3i:b6d1cc6d-896c-40fe-9403-b5b7682b1d03'], # ID of the digital Twin
'messageType': 'getValueRequest', # message Type
'replyToEndpoint': 's3ibs://s3i:7f46a255-3245-439e-84f0-090f0b221965', # Endpoint of my private HMI
'attributePath': 'attributes/features/ml40::Composite/targets/ml40::Engine/features/ml40::RotationalSpeed/rpm' # the path of rpm value
}
```
The message can be sent via the S³I Broker API or using S³I Python lib as well.
\ No newline at end of file
Quick Start
==============================
Reference implementation of the forest modelling language 4.0 (fml 4.0) which is explained in a [KWH 4.0 white paper](https://www.kwh40.de/wp-content/uploads/2020/03/KWH40-Standpunkt-fml40-Version-1.0.pdf).
To use the fml40 reference implementation package in your own project you can install it using the latest [wheel](https://git.rwth-aachen.de/kwh40/fml40-reference-implementation/-/jobs/artifacts/master/raw/public/ml-0.1-py3-none-any.whl?job=wheel).
To install this wheel, go to the respective directory or switch to your designated virtual environment and install the .whl file, just run pip install https://git.rwth-aachen.de/kwh40/fml40-reference-implementation/-/jobs/artifacts/master/raw/public/ml-0.1-py3-none-any.whl?job=wheel
Features
--------
- launching digital twins in persistent mode via `fml40s.py`. You need to register a thing identity for your Digital Twin via [S³I Config REST API](https://config.s3i.vswf.dev/apidoc)
- extending the capabilities with user defined digital twins. We provide an example in [Jupyter Notebook](https://mybinder.org/v2/gh/kwh40/notebooks/master), refer to notebooks `08a` and `08b`.
Usage
-----
A digital twin can be launched in persistent mode by executing
```
python fml40s.py launch <CONFIG_FILE>
```
all the config files must be located in the folder `configs`.
For more options call just run
```
python fml40s.py -h
```
Config files
------------
A valid configuration consists of exactly one json object. Mandatory fields of the json object are
- thingId
- policyId and
- attributes
MyDigitalTwin.json:
``` example
{
"thingId": "s3i:id",
"policyId": "s3i:id",
"attributes": {
"class": "ml40::Thing",
"name": "my digital twin",
"roles": [
{
"class": "fml40::Harvester"
}
],
"features": [
{
"class": "fml40::ProvidesProductionData"
},
{
"class": "ml40::Location",
"longitude": 5.03424,
"latitude": 52.52345
}
]
}
}
```
Structure
---------
### Configs
This folder contains example configuration files in json format for some digital twins.
### ml
This directory includes the implementations of the fml40 python reference implementation as described in the fml40 white paper.
### demo
Example of creating and launching a harvester. Additionally, a hmi is created to communicate with the harvester using S³I-B protocol.
### logs
is composed of the logging files of the created digital twins.
### tests
contains all test scripts (currently in development)
\ No newline at end of file
......@@ -8,7 +8,7 @@
import argparse
from ml.tools import load_config
import os
from ml.dt_factory import create_dt_ref
from ml.dt_factory import create_thing
root_path = os.path.abspath("")
......@@ -41,13 +41,13 @@ def main():
dt_name = list()
for path, dir_list, file_list in g:
for file_name in file_list:
dt_name.append(load_config(file_name, root=root_path)["attributes"].get("name", None))
dt_name.append(load_config(os.path.json(root_path, "configs", file_name))["attributes"].get("name", None))
print(dt_name)
elif args.command == "launch":
dt_config = args.dt_config[0]
try:
model = load_config(dt_config, root=root_path)
model = load_config(os.path.join(root_path, "configs", dt_config))
except FileNotFoundError:
print("{} can not be found".format(dt_config))
return
......@@ -63,7 +63,7 @@ def main():
elif "n" == is_repo.lower():
is_repo = False
dt = create_dt_ref(model=model, grant_type="client_credentials",
dt = create_thing(model=model, grant_type="client_credentials",
secret=secret,
is_broker_rest=True,
is_broker=True,
......
......@@ -162,11 +162,11 @@
def build_sub_features(feature_ins, feature):
"""
build the ml40/fml40 sub features
Build a ml40/fml40 sub features
:param feature_ins: ml40/fml40 feature instance
:param feature_ins: ml40/fml40 feature instance, which has a sub feature to be built
:type feature_ins: object
:param feature: ml40/fml40 feature
:param feature: ml40/fml40 feature containing subFeatures
:type feature: dict
"""
......@@ -191,11 +191,11 @@ def build_sub_features(feature_ins, feature):
def build_sub_thing(feature_ins, feature):
"""
build fml40 sub thing to ml40/fml40 feature
Build and insert a sub thing into a feature instance
:param feature_ins: ml40/fml40 feature instance
:type feature_ins: object
:param feature: ml40/fml40 feature
:param feature: ml40/fml40 feature, which contains a sub thing
:type feature: dict
"""
......@@ -208,7 +208,7 @@ def build_sub_thing(feature_ins, feature):
def build(thing, attributes):
"""
build ml40 thing
Build a ml40 thing instance
:param thing: ml40 thing instance
:type thing: object
......@@ -232,7 +232,7 @@ def build(thing, attributes):
def build_role(role):
"""
build ml40/fml40 role in a ml40 thing instance
Build and insert a ml40/fml40 role in a ml40 thing instance
:param role: ml40/fml40 role
:type role: dict
......@@ -251,7 +251,7 @@ def build_role(role):
def build_feature(feature):
"""
build ml40/fml40 feature in a ml40 thing instance
Build and insert a ml40/fml40 feature in a ml40 thing instance
:param feature: ml40/fml40 feature
:type feature: dict
......@@ -280,7 +280,7 @@ def build_feature(feature):
def add_function_impl_obj(thing, impl_obj, feature_name):
"""
add user-specific implemented object to a thing instance
Add user-specific implemented object to a thing instance
:param thing: ml40 thing instance
:type thing: object
......@@ -307,7 +307,7 @@ 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
Create and launch a ml40 thing with the connection to S³I
:param model: JSON of a ml40::thing model
:type model: dict
......
......@@ -125,7 +125,8 @@ def make_thing_config(thing_id, name, roles, features=[], config_path=""):
def load_config(config_filepath):
"""load 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
......@@ -138,7 +139,7 @@ def load_config(config_filepath):
def find_broker_endpoint(dir_obj, thing_id):
"""
find the S3I-B endpoint of a thing
Find the S3I-B endpoint of a thing
:param dir_obj: S³I Directory Object
:type dir_obj: object
......@@ -155,7 +156,7 @@ def find_broker_endpoint(dir_obj, thing_id):
def remove_namespace(input_str):
"""
remove the namespace like ml40 or fml40
Remove the specified namespace like ml40 or fml40
:param input_str: input with namespace
:type input_str: str
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment