@@ -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