{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Twin Property Tutorial","description":"Accelerate E&P application development and protect your innovation by consuming our Data and Domain APIs / Platform APIs.","lang":"en-US","meta":[{"name":"robots","content":"noindex"}],"llmstxt":{"hide":true,"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"twin-property-tutorial","__idx":0},"children":["Twin Property Tutorial"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This tutorial provides information on how to use the TwinProperty module of SDK to subscribe to changes in Desired properties and write the Reported properties. It uses Redis as the data store. The properties must be associated to a Group Name."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This module allows more than 1 Edge application to access the same Group Name and Desired property with Redis acting as the shared repository for information."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Namespace: ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Agora.Edge"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A singleton member of Agora.SDK called Twin is used by applications to interact with twin properties."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Configuration"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The module uses Redis as the data store, hence, has dependency on the SDK's Redis module. The Edge application must have the following configuration in its primary configuration file"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n    \"AEA2\":{\n        \"RedisClient\": {\n            \"Server\": \"localhost\",\n            \"Port\": 6379,\n        }\n    }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Additionally, the application should connect to Redis using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Redis.Connect()"]}," method."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"_","__idx":1},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#tab/net"},"children":["NET"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Properties:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Instance: TwinProperty"]}," - Used to access the singleton instance and is the same as using the Agora.SDK.TwinProperty."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Event"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ChangeEvent"]}," - Used to subscribe/unsubscribe to a Desired property or a Group Name"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Methods:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["public bool SetReportedProperty(string propName, string propValue, string tpId)"]}," - Method to set reported property of name ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["propName"]}," associated to group name ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tpId"]}," with value ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["propValue"]},". The method uses redis key pattern as mentioned below. :-"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tpId}/reported/{propName}\ntwin_properties/{tpId}/reported/__appname\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["public string GetDesiredProperty(string propName, string tpId)"]}," - Method to read the value of a desired property i.e. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["propName"]}," associated to a GroupName ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tpId"]},". The method uses the key pattern as mentioned below.:-"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tpId}/desired/{propName}\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["public string GetReportedProperty(string propName, string tpId)"]}," - Method to read the value of a reported property ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["propName"]}," assoicated to group name ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tpId"]},". The method uses key pattern as mentioned below:-"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tpId}/reported/{propName}\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"_-1","__idx":2},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#tab/python"},"children":["Python"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["def observe(app_callback:Any, tp_group_id:str, property_name:str=None)"]}," - Method to add observable for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tp_group_id"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["property_name"]},". If ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["property_name"]}," set to None, observable is set for Redis Key pattern:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tp_group_id}/desired/*\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["def stop_observe(tp_group_id:str,property_name:str=None)"]}," - Method to remove observable for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tp_group_id"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["property_name"]}," if it exists. If ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["property_name"]}," set to None, observable is removed for the pattern:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tp_group_id}/desired/*\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["def set_reported_property(prop_name:str, prop_value:any, tp_group_id:str):"]}," - Method to set reported property. The method uses redis key as mentioned below. :-"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tp_group_id}/reported/\ntwin_properties/{tp_group_id}/reported/__appname\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["def get_desired_property(prop_name:str, tp_group_id:str):"]}," - Method to read the value of a desired property i.e. prop_name. The method uses the key pattern as mentioned below. :-"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tp_group_id}/desired/\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["def get_reported_property(prop_name:str, tp_group_id):"]}," - Method to read the value of a reported property prop_name. The method uses key pattern as mentioned below:-"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"twin_properties/{tp_group_id}/reported/\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"for-local-development-and-testing","__idx":3},"children":["For Local Development and Testing"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"_-2","__idx":4},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#tab/net"},"children":["NET"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"create-a-simple-console-net-application","__idx":5},"children":["Create a Simple Console .NET Application"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using System.Text.Json;\nusing static Agora.SDK;\n\nnamespace redis_sdk_project\n{\n   public class Program\n   {\n       public static void appCallback(string groupId, Dictionary<string, string> data)\n       {\n           foreach (var kvp in data)\n           {\n               $\"AppCallback called for group {groupId} and the Key: {kvp.Key}, and Value: {kvp.Value}\\n\".LogInfo();\n           }\n       }\n\n       public static void Main(string[] args)\n       {\n           Redis.Connect(30);\n           var propName = \"TwinPropName\";\n           var tpGroupId = \"Group2\";\n           //Add subscription to all desired properties associated to a GroupName\n           Twin[\"Group1\"].ChangeEvent += appCallback;        \n           //Add subscription to a particular desired property associated to a GroupName\n           Twin[\"Group2\"][propName].ChangeEvent += appCallback2;            \n           //Set reported Property\n           Twin.SetReportedProperty(propName, \"30\", tpGroupId);\n           //Get desired property value\n           $\"Desired Property:{propName}- {Twin.GetDesiredProperty(propName, tpGroupId)}\".LogInfo();\n           //Get reported property\n           $\"Reported Property:{propName}- {Twin.GetReportedProperty(propName, tpGroupId)}\".LogInfo();\n           //Remove subscription\n           Twin[\"Group2\"][propName].ChangeEvent -= appCallback2;\n           Console.ReadLine();\n       }\n   }\n}\n\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"_-3","__idx":6},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#tab/python"},"children":["Python"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"create-a-simple-console-application-in-python","__idx":7},"children":["Create a Simple Console Application in Python"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"from agora_twin_property import Twin\nfrom agora_logging import * \n\n\ndef callback(twin:any):\n    logger.info(f\"callback {twin.tp_id}: {twin.key}: {twin.value}\")\n\ndef callback1(twin:any):\n    logger.info(f\"callback 1 {twin.tp_id}: {twin.key} :{twin.value}\")\n    \n#Subscribe to all desired properties under a GroupName\nTwin.observe(callback, \"Group1\")\n#Subscribe to a desired property under a GroupName\nTwin.observe(callback1,\"Group2\",\"TwinPropName\")\n\n# Set reported property \nlogger.info(f\"Set property {Twin.set_reported_property(\"TwinPropName\",30,\"Group1\")}\")\n# Get reported property\nlogger.info(f\"Reported property: TwinPropName- {Twin.get_reported_property(\"TwinPropName\",\"Group1\")}\")\n# Get desired property\nlogger.info(f\"Desired Property: TwinPropName- {Twin.get_desired_property(\"TwinPropName\",\"Group1\")}\")\n#Stop Observable\nTwin.stop_observe(\"Group1\")\n\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["On running the application locally, application should connect and print the below output."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"/assets/twinpropertysharp.c51c1c249b8e9a5edba22723663791df85334d963b7da8500237e2efd3c69b7f.83eefca3.png","alt":"TwinProperty"},"children":[]}]}]},"headings":[{"value":"Twin Property Tutorial","id":"twin-property-tutorial","depth":2},{"value":"","id":"_","depth":1},{"value":"","id":"_-1","depth":1},{"value":"For Local Development and Testing","id":"for-local-development-and-testing","depth":3},{"value":"","id":"_-2","depth":1},{"value":"Create a Simple Console .NET Application","id":"create-a-simple-console-net-application","depth":3},{"value":"","id":"_-3","depth":1},{"value":"Create a Simple Console Application in Python","id":"create-a-simple-console-application-in-python","depth":3}],"frontmatter":{"seo":{"title":"Twin Property Tutorial"}},"lastModified":"2025-12-26T09:28:10.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/solutions/agora/tutorial/twinproperty","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}