Skip to content

Commit 9b40e80

Browse files
committed
Add dynamic profile support
1 parent 24c6914 commit 9b40e80

1 file changed

Lines changed: 104 additions & 2 deletions

File tree

udi_interface/interface.py

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ class pub(object):
7373
[19, 'oauth', None],
7474
[20, 'webhook', None],
7575
[21, 'bonjour', None],
76-
[22, 'del_node_done', None]
76+
[22, 'del_node_done', None],
77+
[23, 'getprofile', None],
78+
[24, 'updateprofile', None]
7779
]
7880

7981
# topics is a set of key/value pairs holding the callbacks for each
@@ -219,6 +221,8 @@ class Interface(object):
219221
WEBHOOK = pub.topic_list[20][0]
220222
BONJOUR = pub.topic_list[21][0]
221223
DELNODEDONE = pub.topic_list[22][0]
224+
PROFILE = pub.topic_list[23][0]
225+
UPDATEPROFILEDONE = pub.topic_list[24][0]
222226

223227
"""
224228
Polyglot Interface Class
@@ -381,7 +385,7 @@ def _message(self, mqttc, userdata, msg):
381385
'status', 'shortPoll', 'longPoll', 'delete',
382386
'config', 'getIsyInfo', 'getAll', 'custom', 'setLogLevel',
383387
'getNsInfo', 'discover', 'nsdata', 'setController', 'setPoll',
384-
'oauth', 'webhook', 'bonjour' ]
388+
'oauth', 'webhook', 'bonjour', 'getprofile', 'updateprofile' ]
385389

386390
parsed_msg = json.loads(msg.payload.decode('utf-8'))
387391
#LOGGER.debug('MQTT Received Message: {}: {}'.format(msg.topic, parsed_msg))
@@ -1070,6 +1074,10 @@ def _handleInput(self, key, item, published):
10701074
pub.publish(self.WEBHOOK, None, item)
10711075
elif key == 'bonjour':
10721076
pub.publish(self.BONJOUR, None, item)
1077+
elif key == 'getprofile':
1078+
pub.publish(self.PROFILE, None, item)
1079+
elif key == 'updateprofile':
1080+
pub.publish(self.UPDATEPROFILEDONE, None, item)
10731081

10741082
def _handleAddNode(self, result):
10751083
try:
@@ -1393,6 +1401,100 @@ def updateProfile(self):
13931401
message = {'installprofile': {'reboot': False}}
13941402
self.send(message, 'system')
13951403

1404+
def getJsonProfile(self):
1405+
""" Request the current profile """
1406+
minimumVersion = '3.4.3'
1407+
if self.pg3MinimumVersion(minimumVersion):
1408+
LOGGER.info('Sending request to get the existing profile command to Polyglot.')
1409+
self.send({'getprofile': {}}, 'system')
1410+
else:
1411+
LOGGER.error('getJsonProfile failed. PG3 version {} < {}'.format(self.pg3Version, minimumVersion))
1412+
1413+
def updateJsonProfile(self, profile):
1414+
"""
1415+
Update the JSON profile with validation for delete and add/replace operations.
1416+
1417+
Example:
1418+
{
1419+
"delete": {
1420+
"editors": [ "I3_LOAD_4", "I3_ON_OFF" ],
1421+
"nodedefs": [ "*" ],
1422+
"linkdefs": [ "*" ]
1423+
},
1424+
"editors": [ /* ... */ ],
1425+
"nodedefs": [ /* ... */ ],
1426+
"linkdefs": [ /* ... */ ]
1427+
}
1428+
1429+
delete Section Rules:
1430+
• Optional. If omitted, nothing is deleted.
1431+
• For each category (editors, nodedefs, linkdefs):
1432+
• "*" → Delete all items in that category.
1433+
• List of IDs → Delete only the specified items.
1434+
• Items not listed are preserved.
1435+
Add/Replace Rules:
1436+
• Any editors, nodedefs, or linkdefs provided:
1437+
• Replace existing items with the same id.
1438+
• Add new items if no matching id exists.
1439+
1440+
Raises:
1441+
ValueError: If profile validation fails
1442+
"""
1443+
minimumVersion = '3.4.3'
1444+
if self.pg3MinimumVersion(minimumVersion):
1445+
if not isinstance(profile, dict):
1446+
LOGGER.error('Profile must be a dictionary')
1447+
raise ValueError('Profile must be a dictionary')
1448+
1449+
# Validate delete section if present
1450+
if 'delete' in profile:
1451+
delete_section = profile['delete']
1452+
if not isinstance(delete_section, dict):
1453+
LOGGER.error('Delete section must be a dictionary')
1454+
raise ValueError('Delete section must be a dictionary')
1455+
1456+
valid_categories = {'editors', 'nodedefs', 'linkdefs'}
1457+
invalid_categories = set(delete_section.keys()) - valid_categories
1458+
1459+
if invalid_categories:
1460+
LOGGER.error(f'Invalid delete keys: {invalid_categories}')
1461+
raise ValueError(f'Invalid delete keys: {invalid_categories}')
1462+
1463+
for category, items in delete_section.items():
1464+
if not isinstance(items, list):
1465+
LOGGER.error(f'Delete items for {category} must be a list of IDs')
1466+
raise ValueError(f'Delete items for {category} must be a list of IDs')
1467+
1468+
if isinstance(items, list) and not all(isinstance(item, str) for item in items):
1469+
LOGGER.error(f'All IDs in {category} delete list must be strings')
1470+
raise ValueError(f'All IDs in {category} delete list must be strings')
1471+
1472+
# Validate add/replace sections
1473+
valid_sections = {'editors', 'nodedefs', 'linkdefs'}
1474+
for section in valid_sections:
1475+
if section in profile:
1476+
if not isinstance(profile[section], list):
1477+
LOGGER.error(f'{section} must be a list')
1478+
raise ValueError(f'{section} must be a list')
1479+
1480+
for item in profile[section]:
1481+
if not isinstance(item, dict):
1482+
LOGGER.error(f'Each item in {section} must be a dictionary')
1483+
raise ValueError(f'Each item in {section} must be a dictionary')
1484+
1485+
if 'id' not in item:
1486+
LOGGER.error(f'Each item in {section} must have an "id" field')
1487+
raise ValueError(f'Each item in {section} must have an "id" field')
1488+
1489+
if not isinstance(item['id'], str):
1490+
LOGGER.error(f'ID field in {section} must be a string')
1491+
raise ValueError(f'ID field in {section} must be a string')
1492+
1493+
# If all validations pass, send the message
1494+
LOGGER.info('Sending profile update request to Polyglot.')
1495+
self.send({'updateprofile': profile}, 'system')
1496+
else:
1497+
LOGGER.error('updateJsonProfile failed. PG3 version {} < {}'.format(self.pg3Version, minimumVersion))
13961498

13971499
# TODO:
13981500
def addNoticeTemp(self, key, text, delaySec):

0 commit comments

Comments
 (0)