Handle param information from xml and json

* Added handilng to add description from BBF's DM xml file to converted json file.
* Change DM aggregation in temp txt file from CSV format to JSON
* Added "dm_json_files" in tools_input.json file to define the path of Json files having more details about DM parameters
* Added support to read DM param description from json plugin and update it in "datamodel_default.xml"
* Added support to read the description from defined "dm_json_files" in case description of param is not present in json plugin
* Added support to read the description from defined "dm_json_files" in case datamodel param defined in 'C' code
* Added enum and list type DM parameter in generated "datamodel_default.xml"
This commit is contained in:
Suvendhu Hansa 2022-09-28 08:46:30 +00:00 committed by Amin Ben Ramdhane
parent 01d8dd9487
commit 248a8e2053
11 changed files with 9185 additions and 9911 deletions

View file

@ -954,6 +954,20 @@ Examples:
==> Generate all required files defined in tools_input.json file
```
The parameters/keys used in tools_input.json file are mostly self-explanatory but few parameters are required a bit more details.
| Key | Description |
|-----|-------------|
| vendor_list | This option should have the same name of the vendor directory names |
| dm_json_files | This should contain the list of json file path, where each file contains the definition of DM objects/parameters |
| vendor_prefix | The prefix used by vendor for vendor extension in DM objects/parameters |
| output.acs | Currently the tool support two variants of xml definitions of DM objects/parameters |
| | hdm: This variant of xml is compatible with Nokia HDM ACS |
| | default: This contains the generic definition which has the capability to define more descriptive DM objects/parameters |
| output.file_format | xls: An excel file listing the supported and unsupported DM objects/parameters |
> Note: To add more description about the vendor extended DM objects/parameters, it is required to add the definition of the required/related DM objects/parameters in a json file (The json structure should follow same format as given in [tr181.json](./dmtree/json/tr181.json)), The same json file need to be defined in dm_json_files list.
The input json file should be defined as follow:
```bash
@ -969,6 +983,10 @@ The input json file should be defined as follow:
"openwrt",
"test"
],
"dm_json_files": [
"../dmtree/json/tr181.json",
"../dmtree/json/tr104.json"
]
"vendor_prefix": "X_IOPSYS_EU_",
"plugins": [
{
@ -1030,9 +1048,9 @@ The input json file should be defined as follow:
> Note1: For the local repository, you must use an absolute path as repo option.
> Note2: If proto is not defined in the json config file, then git is used by default as proto option.
> Note2: If proto is not defined in the json config file, then git is used by default as proto option.
- For more examples of tools input json file, you can see this link: [tools_input.json](./devel/tools/tools_input.json)
- For more examples of tools input json file, you can see this link: [tools_input.json](./tools/tools_input.json)
# How to expose datamodel over ubus directly with the help of libbbf APIs

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -25,10 +25,6 @@ echo "Validate BBF TR-104 JSON Plugin"
./tools/validate_json_plugin.py dmtree/json/tr104.json
check_ret $?
echo "Validate BBF TR-135 JSON Plugin"
./tools/validate_json_plugin.py dmtree/json/tr135.json
check_ret $?
echo "Validate X_IOPSYS_EU_Dropbear JSON Plugin"
./tools/validate_json_plugin.py test/files/etc/bbfdm/json/X_IOPSYS_EU_Dropbear.json
check_ret $?

View file

@ -127,6 +127,22 @@ def get_protocol_from_json(value):
else:
return "BBFDM_USP"
def get_description_from_json(value):
val = get_option_value(value, "description", "")
return val
def get_range_from_json(value):
val = get_option_value(value, "range", [])
return val
def get_list_from_json(value):
val = get_option_value(value, "list", {})
return val
def get_enum_from_json(value):
val = get_option_value(value, "enumerations", [])
return val
def clean_supported_dm_list():
LIST_SUPPORTED_DM.clear()
@ -142,10 +158,12 @@ def fill_data_model_file():
fp = open(DATA_MODEL_FILE, 'a', encoding='utf-8')
for value in LIST_SUPPORTED_DM:
if (ROOT):
if (value.startswith(ROOT)):
print(f'{value}', file=fp)
js_val = json.loads(value)
param = get_option_value(js_val, "param")
if param is not None and (param.startswith(ROOT)):
print(f"{value}", file=fp)
else:
print(f'{value}', file=fp)
print(f"{value}", file=fp)
fp.close()
@ -217,7 +235,7 @@ def generate_datamodel_tree(filename):
full_obj_name = obj_name + ".{i}."
LIST_SUPPORTED_DM.append(
full_obj_name + "," + obj_permission + ",DMT_OBJ" + "," + obj_protocol)
"{\"param\":\"" + full_obj_name + "\",\"permission\":\"" + obj_permission + "\",\"type\":\"DMT_OBJ\",\"protocol\":\"" + obj_protocol + "\"}")
if obj[8] != "NULL":
LIST_OBJ.append(full_obj_name + ":" + obj[8])
@ -241,7 +259,7 @@ def generate_datamodel_tree(filename):
param_protocol = param[5].replace("}", "").replace(" ", "")
LIST_SUPPORTED_DM.append(
param_name + "," + param_permission + "," + param_type + "," + param_protocol)
"{\"param\":\"" + param_name + "\",\"permission\":\"" + param_permission + "\",\"type\":\"" + param_type + "\",\"protocol\":\"" + param_protocol + "\"}")
if value not in LIST_DEL_PARAM:
LIST_DEL_PARAM.append(value)
@ -290,9 +308,10 @@ def parse_dynamic_json_datamodel_tree(obj, value):
obj_permission = "DMWRITE" if get_option_value(
value, "access") is True else "DMREAD"
obj_protocols = get_protocol_from_json(value)
obj_description = get_description_from_json(value)
obj_name = obj.replace("{BBF_VENDOR_PREFIX}", BBF_VENDOR_PREFIX)
LIST_SUPPORTED_DM.append(obj_name + "," + obj_permission + ",DMT_OBJ" + "," + obj_protocols)
LIST_SUPPORTED_DM.append("{\"param\":\"" + obj_name + "\",\"permission\":\"" + obj_permission + "\",\"type\":\"DMT_OBJ\",\"protocol\":\"" + obj_protocols + "\",\"description\":\"" + obj_description + "\"}")
hasobj = obj_has_child(value)
hasparam = obj_has_param(value)
@ -307,8 +326,13 @@ def parse_dynamic_json_datamodel_tree(obj, value):
param_permission = "DMWRITE" if get_option_value(
v, "write") is True else "DMREAD"
param_protocols = get_protocol_from_json(v)
param_list = get_list_from_json(v)
param_enums = get_enum_from_json(v)
param_desc = get_description_from_json(v)
param_range = get_range_from_json(v)
LIST_SUPPORTED_DM.append(
param_name + "," + param_permission + "," + param_type + "," + param_protocols)
"{\"param\":\"" + param_name + "\",\"permission\":\"" + param_permission + "\",\"type\":\"" + param_type + "\",\"protocol\":\"" + param_protocols + "\",\"description\":\"" + param_desc + "\",\"list\":" + json.dumps(param_list) + ",\"range\":" + json.dumps(param_range) + ",\"enum\":" + json.dumps(param_enums) + "}")
break
if hasobj and isinstance(value, dict):
@ -511,3 +535,67 @@ def generate_supported_dm(vendor_prefix=None, vendor_list=None, plugins=None):
############### COPY SUPPORTED DATA MODEL TO FILE ###############
remove_file(DATA_MODEL_FILE)
fill_data_model_file()
def get_param_info_from_json(data, dm_json_files=None, info="description"):
arr = data.split(".")
list_data = []
if len(arr) == 0:
return None
for i in range(0, len(arr)):
string = ""
if i == 0:
string=arr[i] + "."
elif i == (len(arr) - 1):
string=arr[i]
else:
for j in range(0, i + 1):
string=string + arr[j]
string=string + "."
if len(string) != 0:
list_data.append(string)
if len(list_data) == 0:
return None
found = False
res = None
if dm_json_files is not None and isinstance(dm_json_files, list) and dm_json_files:
for fl in dm_json_files:
if os.path.exists(fl):
f = open(fl, 'r', encoding='utf-8')
try:
ob = json.load(f)
except json.decoder.JSONDecodeError:
continue
index = -1
for key in ob.keys():
if key in list_data:
index = list_data.index(key)
break
if index == -1:
continue
for i in range(index, len(list_data)):
if i != (len(list_data) - 1) and list_data[i + 1] == list_data[i] + "{i}.":
continue
try:
ob = ob[list_data[i]]
found = True
except KeyError:
found = False
break
if found is True:
try:
res = ob[info]
break
except KeyError:
res = None
return res

View file

@ -13,6 +13,8 @@ from collections import OrderedDict
from shutil import copyfile
import bbf_common as bbf
desc_dict = {}
listTypes = ["string",
"unsignedInt",
"unsignedLong",
@ -510,6 +512,7 @@ def printOBJ(dmobject, hasobj, hasparam, bbfdm_type):
print("\"type\" : \"object\",", file=fp)
print("\"version\" : \"%s\"," % dmobject.get('version'), file=fp)
print("\"protocols\" : [%s]," % bbfdm_type, file=fp)
print("\"description\" : \"%s\"," % getParamDesc(dmobject, None), file=fp)
if uniquekeys is not None:
print("\"uniqueKeys\" : [%s]," % uniquekeys, file=fp)
if dmobject.get('access') == "readOnly":
@ -527,8 +530,7 @@ def printOBJ(dmobject, hasobj, hasparam, bbfdm_type):
def printPARAM(dmparam, dmobject, bbfdm_type):
hasmapping, mapping = getparammapping(dmobject, dmparam)
islist, datatype, paramvalrange, paramenum, paramunit, parampattern, listminItem, listmaxItem, listmaxsize = getparamoption(
dmparam)
islist, datatype, paramvalrange, paramenum, paramunit, parampattern, listminItem, listmaxItem, listmaxsize = getparamoption(dmparam)
fp = open('./.json_tmp', 'a', encoding='utf-8')
print("\"%s\" : {" % dmparam.get('name').replace(" ", ""), file=fp)
@ -542,6 +544,7 @@ def printPARAM(dmparam, dmobject, bbfdm_type):
print("\"version\" : \"%s\"," % dmparam.get('version'), file=fp)
print("\"protocols\" : [%s]," % bbfdm_type, file=fp)
print("\"description\" : \"%s\"," % getParamDesc(dmparam, datatype), file=fp)
default = getParamDefault(dmparam)
if default is not None and len(default) != 0 and default != "\"":
@ -893,6 +896,96 @@ def generatejsonfromobj(pobj, pdir):
return dmfp.name
def processDatatypes(datatype):
key = None
enum = None
des = None
if datatype.tag == "dataType":
key = datatype.get("name")
if key is None or len(key) == 0:
return
if key in desc_dict.keys():
return
for dt in datatype:
if dt.tag == "description":
des = dt.text
break
if des is None:
desc_dict[key] = ""
return
elif des.find('{{enum}}') != -1:
for c in datatype:
if c.tag == "string":
for e in c:
if e.tag == "enumeration":
if enum is None:
enum = "Enumeration of: " + e.get("value")
else:
enum = enum + ", " + e.get("value")
if enum is not None:
enum = enum + "."
des = re.sub('{{enum}}', enum, des)
des = des.replace("{", "<")
des = des.replace("}", ">")
des = des.replace("\'", "")
des = des.replace("\"", "")
desc_dict[key] = des
def getParamDesc(dmparam, key):
text = None
detail = None
enum = None
for s in dmparam:
if s.tag == "description":
text = s.text
if text is None or len(text) == 0:
return ""
if text.find('{{enum}}') != -1:
for s in dmparam:
if s.tag != "syntax":
continue
for c in s:
if c.tag != "string":
continue
for e in c:
if e.tag == "enumeration":
if enum is None:
enum = "Enumeration of: " + e.get("value")
else:
enum = enum + ", " + e.get("value")
if enum is not None:
enum = enum + "."
text = re.sub('{{enum}}', enum, text)
if text.find('{{datatype|expand}}') != -1 and key is not None:
try:
detail = desc_dict[key]
except KeyError:
detail = None
if detail is not None:
text = re.sub('{{datatype|expand}}', detail, text)
text = text.replace("{", "<")
text = text.replace("}", ">")
text = text.replace("\'", "")
text = text.replace("\"", "")
text = text.replace("\n", "")
desc = ' '.join(text.split())
return desc
### main ###
if len(sys.argv) < 4:
printusage()
@ -908,6 +1001,9 @@ xmlroot1 = tree1.getroot()
model1 = xmlroot1
for child in model1:
if child.tag == "dataType":
processDatatypes(child)
if child.tag == "model":
model1 = child
@ -939,6 +1035,9 @@ if "tr-181" in sys.argv[1] or "tr-104" in sys.argv[1]:
model2 = xmlroot2
for child in model2:
if child.tag == "dataType":
processDatatypes(child)
if child.tag == "model":
model2 = child

View file

@ -36,6 +36,7 @@ VENDOR_PREFIX = None
VENDOR_LIST = None
PLUGINS = None
OUTPUT = None
DM_JSON_FILES = None
json_file = open(sys.argv[1], "r", encoding='utf-8')
json_data = json.loads(json_file.read())
@ -78,6 +79,10 @@ for option, value in json_data.items():
VENDOR_LIST = value
continue
elif option == "dm_json_files":
DM_JSON_FILES = value
continue
elif option == "plugins":
PLUGINS = value
continue
@ -113,10 +118,10 @@ if isinstance(file_format, list):
bbf.clean_supported_dm_list()
output_file_name = output_dir + '/' + output_file_prefix + '_' + acs_format + '.xml'
if acs_format == "hdm":
bbf_xml.generate_xml('HDM', output_file_name)
bbf_xml.generate_xml('HDM', DM_JSON_FILES, output_file_name)
if acs_format == "default":
bbf_xml.generate_xml('default', output_file_name)
bbf_xml.generate_xml('default', DM_JSON_FILES, output_file_name)
if _format == "xls":

View file

@ -30,8 +30,11 @@ def getprotocols(value):
def is_param_obj_command_event_supported(dmobject):
for value in bbf.LIST_SUPPORTED_DM:
obj = value.split(",")
if obj[0] == dmobject:
obj = json.loads(value)
param = bbf.get_option_value(obj, "param", None)
if param is None:
continue
if param == dmobject:
bbf.LIST_SUPPORTED_DM.remove(value)
return "Yes"
return "No"
@ -78,7 +81,11 @@ def parse_dynamic_object(dm_name_list):
return None
for value in bbf.LIST_SUPPORTED_DM:
obj = value.split(",")
obj = json.loads(value)
param = bbf.get_option_value(obj, "param", None)
p_type = bbf.get_option_value(obj, "type", None)
if param is None or p_type is None:
continue
for dm in dm_name_list:
@ -87,17 +94,17 @@ def parse_dynamic_object(dm_name_list):
if JSON_FILE is None:
continue
if dm == "tr181" and ".Services." in obj[0]:
if dm == "tr181" and ".Services." in param:
continue
if dm == "tr104" and ".Services." not in obj[0]:
if dm == "tr104" and ".Services." not in param:
continue
if dm == "tr135" and ".Services." not in obj[0]:
if dm == "tr135" and ".Services." not in param:
continue
dmType = "object" if obj[2] == "DMT_OBJ" else "parameter"
add_data_to_list_dm(obj[0], "Yes", "CWMP+USP", dmType)
dmType = "object" if p_type == "DMT_OBJ" else "parameter"
add_data_to_list_dm(param, "Yes", "CWMP+USP", dmType)
def parse_object_tree(dm_name_list):

View file

@ -9,6 +9,7 @@ import argparse
import xml.etree.ElementTree as ET
import xml.dom.minidom as MD
import bbf_common as bbf
import json
DM_OBJ_COUNT = 0
DM_PARAM_COUNT = 0
@ -36,7 +37,7 @@ def pretty_format(elem):
return reparsed.toprettyxml(indent=" ")
def generate_bbf_xml_file(output_file):
def generate_bbf_xml_file(output_file, dm_json_files=None):
global DM_OBJ_COUNT
global DM_PARAM_COUNT
@ -54,30 +55,168 @@ def generate_bbf_xml_file(output_file):
for value in bbf.LIST_SUPPORTED_DM:
obj = value.strip().split(",")
if obj[3] == "BBFDM_USP":
obj = json.loads(value)
protocol = bbf.get_option_value(obj, "protocol", None)
if protocol is None or protocol == "BBFDM_USP":
continue
access = "readOnly" if obj[1] == "DMREAD" else "readWrite"
p_type = bbf.get_option_value(obj, "type", None)
if p_type is None:
continue
if obj[2] == "DMT_OBJ":
name = bbf.get_option_value(obj, "param", None)
permission = bbf.get_option_value(obj, "permission", None)
list_ob = bbf.get_option_value(obj, "list", None)
enum = bbf.get_option_value(obj, "enum", None)
desc = bbf.get_option_value(obj, "description", None)
rang = bbf.get_option_value(obj, "range", None)
if name is None or permission is None:
continue
access = "readOnly" if permission == "DMREAD" else "readWrite"
if p_type == "DMT_OBJ":
# Object
objec = ET.SubElement(model, "object")
objec.set("name", obj[0])
objec.set("name", name)
objec.set("access", access)
objec.set("minEntries", "0")
objec.set("maxEntries", "20")
ob_description = ET.SubElement(objec, "description")
if desc is None or len(desc) == 0:
desc = bbf.get_param_info_from_json(name, dm_json_files, "description")
ob_description.text = desc.replace("<", "{").replace(">", "}") if desc is not None else ""
DM_OBJ_COUNT += 1
else:
# Parameter
subtype = None
list_datatype = None
parameter = ET.SubElement(objec, "parameter")
parameter.set("name", obj[0][obj[0].rindex('.')+1:])
parameter.set("name", name[name.rindex('.')+1:])
parameter.set("access", access)
description = ET.SubElement(parameter, "description")
description.text = str(
"parameter " + obj[0][obj[0].rindex('.')+1:])
p_description = ET.SubElement(parameter, "description")
if desc is None or len(desc) == 0:
desc = bbf.get_param_info_from_json(name, dm_json_files, "description")
p_description.text = desc.replace("<", "{").replace(">", "}") if desc is not None else ""
syntax = ET.SubElement(parameter, "syntax")
ET.SubElement(syntax, ARRAY_TYPES.get(obj[2], None))
if list_ob is None:
list_ob = bbf.get_param_info_from_json(name, dm_json_files, "list")
if list_ob is not None and len(list_ob) != 0:
listtag = ET.SubElement(syntax, "list")
item_ob = None
maxsize = None
# Handle items in list
try:
item_ob = list_ob["item"]
except KeyError:
item_ob = None
if item_ob is not None:
minval = None
maxval = None
try:
minval = item_ob["min"]
except KeyError:
minval = None
try:
maxval = item_ob["max"]
except KeyError:
maxval = None
if minval is not None:
listtag.set("minItems", str(minval))
if maxval is not None:
listtag.set("maxItems", str(maxval))
# Handle maxsize in list
try:
maxsize = list_ob["maxsize"]
except KeyError:
maxsize = None
if maxsize is not None:
sizetag = ET.SubElement(listtag, "size")
sizetag.set("maxLength", str(maxsize))
if enum is None or len(enum) == 0:
try:
enum = list_ob["enumerations"]
except KeyError:
enum = None
try:
list_datatype = list_ob["datatype"]
except KeyError:
list_datatype = None
if list_datatype is not None and list_datatype in ARRAY_TYPES.values():
subtype = ET.SubElement(syntax, list_datatype)
else:
subtype = ET.SubElement(syntax, ARRAY_TYPES.get(p_type, None))
else:
subtype = ET.SubElement(syntax, ARRAY_TYPES.get(p_type, None))
if enum is None:
enum = bbf.get_param_info_from_json(name, dm_json_files, "enumerations")
if enum is not None:
for val in enum:
enumeration = ET.SubElement(subtype, "enumeration")
enumeration.set("value", str(val))
# handle range
range_min = None
range_max = None
if rang is None:
try:
if list_ob is not None:
rang = list_ob["range"]
except KeyError:
rang = None
if rang is None:
rang = bbf.get_param_info_from_json(name, dm_json_files, "range")
if rang is not None and len(rang) != 0:
rang_len = len(rang)
for i in range(rang_len):
try:
range_min = rang[i]["min"]
except KeyError:
range_min = None
try:
range_max = rang[i]["max"]
except KeyError:
range_max = None
if list_datatype is not None:
val_type = list_datatype
else:
val_type = ARRAY_TYPES.get(p_type, None)
if val_type == "string" or val_type == "hexBinary" or val_type == "base64":
size_tag = ET.SubElement(subtype, "size")
if range_min is not None:
size_tag.set("minLength", str(range_min))
if range_max is not None:
size_tag.set("maxLength", str(range_max))
if val_type == "unsignedInt" or val_type == "unsignedLong" or val_type == "int" or val_type == "long":
range_tag = ET.SubElement(subtype, "range")
if range_min is not None:
range_tag.set("minInclusive", str(range_min))
if range_max is not None:
range_tag.set("maxInclusive", str(range_max))
DM_PARAM_COUNT += 1
xml_file = open(output_file, "w", encoding='utf-8')
@ -151,39 +290,49 @@ def generate_hdm_xml_file(output_file):
for value in bbf.LIST_SUPPORTED_DM:
obj = value.strip().split(",")
if obj[3] == "BBFDM_USP":
obj = json.loads(value)
protocol = bbf.get_option_value(obj, "protocol", None)
if protocol is None or protocol == "BBFDM_USP":
continue
if obj[2] == "DMT_OBJ":
p_type = bbf.get_option_value(obj, "type", None)
if p_type is None:
continue
name = bbf.get_option_value(obj, "param", None)
permission = bbf.get_option_value(obj, "permission", None)
if name is None or permission is None:
continue
if p_type == "DMT_OBJ":
# Object
obj_tag = ET.SubElement(
param_array[obj[0].replace(".{i}", "").count('.') - root_dot_count -1], "parameter")
param_array[name.replace(".{i}", "").count('.') - root_dot_count -1], "parameter")
obj_name = ET.SubElement(obj_tag, "parameterName")
obj_name.text = str(obj[0].replace(".{i}", "").split('.')[-2])
obj_name.text = str(name.replace(".{i}", "").split('.')[-2])
obj_type = ET.SubElement(obj_tag, "parameterType")
obj_type.text = str("object")
obj_array = ET.SubElement(obj_tag, "array")
obj_array.text = str(
"true" if obj[0].endswith(".{i}.") else "false")
"true" if name.endswith(".{i}.") else "false")
parameters = ET.SubElement(obj_tag, "parameters")
param_array[obj[0].replace(".{i}", "").count('.') - root_dot_count] = parameters
param_array[name.replace(".{i}", "").count('.') - root_dot_count] = parameters
DM_OBJ_COUNT += 1
else:
# Parameter
param_tag = ET.SubElement(
param_array[obj[0].replace(".{i}", "").count('.') - root_dot_count], "parameter")
param_array[name.replace(".{i}", "").count('.') - root_dot_count], "parameter")
param_name = ET.SubElement(param_tag, "parameterName")
param_name.text = str(obj[0][obj[0].rindex('.')+1:])
param_name.text = str(name[name.rindex('.')+1:])
param_type = ET.SubElement(param_tag, "parameterType")
param_type.text = str(ARRAY_TYPES.get(obj[2], None))
param_type.text = str(ARRAY_TYPES.get(p_type, None))
DM_PARAM_COUNT += 1
xml_file = open(output_file, "w", encoding='utf-8')
xml_file.write(pretty_format(root))
xml_file.close()
def generate_xml(acs = 'default', output_file="datamodel.xml"):
def generate_xml(acs = 'default', dm_json_files=None, output_file="datamodel.xml"):
global DM_OBJ_COUNT
global DM_PARAM_COUNT
@ -196,7 +345,7 @@ def generate_xml(acs = 'default', output_file="datamodel.xml"):
if acs == "HDM":
generate_hdm_xml_file(output_file)
else:
generate_bbf_xml_file(output_file)
generate_bbf_xml_file(output_file, dm_json_files)
if os.path.isfile(output_file):
print(f' - XML file generated: {output_file}')
@ -315,6 +464,6 @@ if __name__ == '__main__':
bbf.generate_supported_dm(args.vendor_prefix, args.vendor_list, plugins)
bbf.clean_supported_dm_list()
generate_xml(args.format, args.output)
generate_xml(args.format, args.dm_json_files, args.output)
print(f'Datamodel generation completed, aritifacts available in {args.output}')
sys.exit(bbf.BBF_ERROR_CODE)

View file

@ -10,6 +10,10 @@
"iopsys",
"openwrt"
],
"dm_json_files": [
"../dmtree/json/tr181.json",
"../dmtree/json/tr104.json"
],
"vendor_prefix": "X_IOPSYS_EU_",
"plugins": [
{
@ -40,11 +44,11 @@
]
},
{
"repo": "https://dev.iopsys.eu/iopsys/twamp.git",
"repo": "https://dev.iopsys.eu/iopsys/twamp-light.git",
"proto": "git",
"version": "master",
"version": "devel",
"dm_files": [
"datamodel.c"
"src/datamodel.c"
]
},
{