These docs are for v1.0.1. Click to read the latest docs for v1.0.11.

Publisher template tutorial

This section runs through using the publisher template we created in IOTICS Host Library.

Prerequisites


You need to have:

Context


In publisher.py the example:

Create a twin and sets its meta data

The update_twin method has parameters for setting some basic metadata to make the twin accessible via text- and location-based searches. Further capabilities for describing twins are shown below.

def _create_twin(self) -> str:
    # Create an twin id in the registrar
    twin_id = self.agent_auth.make_twin_id(TWIN_NAME)

    # Create the twin
    self.twin_api.create_twin(twin_id)
    return twin_id

def _set_twin_meta(self, twin_id: str):
    # The RDF Schema provides "label" and "comment" properties to provide basic details of resources: https://www.w3.org/TR/rdf-schema/#ch_label
    label = 'Twin 1'  # A human-readable version of the twin's name
    comment = 'The first twin we made in Iotics'  # Space to put more free text describing the twin

    # Set twin location to London, using the GeoLocationUpdate class to provide its latitude and longitude.
    # This will make the twin visible in Iotics Cloud and it will enable the search by location.
    london_location = GeoLocationUpdate(location=GeoLocation(lat=51.507359, lon=-0.136439))

    # More information on the parameters of this method is available in the iotics-host-lib source code.
    self.twin_api.update_twin(
        twin_id,
        add_tags=['random', 'awesome'], # Deprecated pre-RDF way of describing twins.
        add_labels=[LangLiteral(value=label, lang='en')],     # Labels and comments must be language-tagged literals --
        add_comments=[LangLiteral(value=comment, lang='en')], # see https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal
        location=london_location, # Must be instance of GeoLocationUpdate, as constructed above.
    )

Getting started with IOTICSpace

In the code snippet above, the London location is added to the twin metadata. This will make the twin visible in Iotics Cloud.

Read more about Iotics Cloud in the Iotics documentation: Getting Started with IOTICSpace.

Adding semantic meta data via property usage

All metadata describing twins is stored using the Resource Description Framework (RDF), which makes statements about the world using subject-predicate-object triples. For purposes of this walkthrough, think of predicates and objects as keys and values describing the subject (usually a Twin or Feed).

In the code snippet below, a custom property is added to the twin while setting the metadata. Custom properties allow the user to set the value of the predicate (the key parameter), an IRI referencing the definition of the property (ie, what sort of thing it describes and how). The object is set using a second parameter, either one of various literal types or a uri -- see the ModelProperty source code.

Twins so decorated may be found in a semantic search based on a set of properties. You can see the follower doing this type of search in its Semantic searches for and follows twins section.

Read more about properties in the Iotics documentation:

def _set_twin_meta(self, twin_id: str):
    category_property = ModelProperty(key='http://data.iotics.com/ns/category',
                                      uri_value=Uri(value='http://data.iotics.com/category/Temperature'))

    self.twin_api.update_twin(
        twin_id,
        add_props=[category_property]
    )

Create a feed and sets its meta data

Feeds are described using many of the same properties as twins, (eg labels and comments), as shown above. Additionally, Feeds have associated Values with details of what sort of data is present in each shared update. For example, the Feed below has one Value, explaining that each share will have a decimal number (not a string or boolean, for instance) representing degrees Celsius. The unit parameter is set to an IRI representing °C in a popular ontology for units of measure.

def _create_feed(self, twin_id: str) -> str:
    feed_name = 'random_temperature_feed'
    self.feed_api.create_feed(twin_id, feed_name)
    return feed_name

def _set_feed_meta(self, twin_id: str, feed_name: str):
    label = 'Random temperature feed'
    comment = f'Awesome feed generating a temperature in Celsius each {self.update_frequency_seconds} seconds'

    self.feed_api.update_feed(
        twin_id, feed_name,
        add_labels=[LangLiteral(value=label, lang='en')],
        add_comments=[LangLiteral(value=comment, lang='en')],
        # Whether this feed's most recent data can be retrieved via the InterestApi
        store_last=True,
        add_tags=['random', 'awesome'],
        add_values=[
            Value(label='temp',
                  data_type=BasicDataTypes.DECIMAL.value,
                  comment='a random temperature in Celsius',
                  unit='http://purl.obolibrary.org/obo/UO_0000027'),
        ]
    )

Publish data to the feed

The data shared to a Feed should be a base64-encoded dict keyed with the Feed's Value labels. You may also explicitly set a time associated with this share.

def _share_feed_data(self, twin_id: str, feed_name: str):
    non_encoded_data = {
        'temp': round(random.uniform(-10.0, 45.0), 2)
    }
    json_data = json.dumps(non_encoded_data)
    try:
        base64_encoded_data = base64.b64encode(json_data.encode()).decode()
    except TypeError as err:
        raise RandomTempPublisherBaseException(
            f'Can not encode data to share from {twin_id}/{feed_name}: {err}, {json_data}'
        ) from err

    self.feed_api.share_feed_data(
        twin_id, feed_name,
        data=base64_encoded_data, mime='application/json',
        occurred_at=datetime.now(tz=timezone.utc).isoformat()
    )

    return non_encoded_data

In conf.py:

Add a configurable publishing frequency

update_frequency_seconds: int = 10

🚧

Note:

The configuration used in this template is built using pydantic (see https://pydantic-docs.helpmanual.io/)

As an example of how this configuration works, you can change this update_frequency_seconds value from its default of 10, by setting the environment variable RANDPUB_UPDATE_FREQUENCY_SECONDS e.g. to set it to 1 second: export RANDPUB_UPDATE_FREQUENCY_SECONDS=1

Tests and checks


🚧

Note:

To run the following commands, you need to setup your own Python virtual environment:

python3 -mvenv env
source ./env/bin/activate
pip install -U pip setuptools

make unit-static-tests # Run unit and static tests using tox

make static-test` # Run static tests only, using tox

make unit-tests # Run unit tests only, using tox

Running locally


Create credentials for the component

Run gen_creds.py script from the iotics-host-lib directory with your resolver URL. If you don't know your resolver URL you can find it by going to your IOTICSpace's index page: https://example.iotics.space/index.json

This will generate:

  • ** USER_SEED and USER_KEY_NAME together are used to generate your agent DID
  • ** AGENT_SEED and AGENT_KEY_NAME together are used to generate your agent DID
# Create a virtual environment:
python3 -mvenv venv
source venv/bin/activate
pip install -U pip setuptools
pip install iotics-identity
# or use an existing one and then call:
./scripts/gen_creds.py --resolver [resolver url e.g. https://your.resolver]

Note: Answer yes (y) to creating a new user seed, and also yes to creating a new agent seed.

Once the script successfully completes, take a note of the variables for your component:

export RESOLVER_HOST=https://your.resolver
export USER_SEED=dec8615d1fc1598ceade592a6d756cad3846d8a2fc9a26af7251df7eb152b771
export USER_KEY_NAME=00
export AGENT_SEED=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
export AGENT_KEY_NAME=00

Those should be kept safe and be present in the environment in which you are running your connector.

If you are using the same user for a publisher component and a follower component, you can reuse the same USER SEED and USER KEY NAME, but note that this should NOT be stored in production environment with your component, instead keep it safe and secure elsewhere.

Run Component

Run this locally in the template directory generated earlier ( random-temp-pub if you chose the default settings for example) with:

# then export some environment variables
export RESOLVER_HOST=[address of resolver for your space]
export USER_SEED=[user seed from above e.g. lmnop5678...]
export USER_KEY_NAME=[user seed from above e.g. 00]
export AGENT_SEED=[agent seed from above e.g. lmnop5678...]
export AGENT_KEY_NAME=[user seed from above e.g. 00]
export QAPI_URL=[address of qapi]

# next either
make docker-run # Run using the docker image
# OR
python3 -mvenv env
source ./env/bin/activate
pip install -U pip setuptools

make setup-dev

make run # Run using the sources from your computer

Note: the QAPI_URL can be found by going to your IOTICSpace's index page: https://example.iotics.space/index.json

📘

Note:

The QAPI_URL can be found by going to your IOTICSpace's index page: https://example.iotics.space/index.json


What's next?

Create a follower to consume the data.