Skip to main content

The ExoSense Client is the Python library for interacting with Exosite's ExoSense Industrial IoT Solution.

Project description

https://travis-ci.com/exosite/lib_exoedge_python.svg?token=tgjcyH1MG5sXqcVsD1kG&branch=master

ExoEdge is the client to ExoSense. ExoSense provides configuration objects that conform to data schemas. ExoEdge interprets these schema-driven objects and configures industrial IoT gateways to give their data to ExoSense according to schema. In other words, ExoEdge is a schema based IIoT gateway that runs on most platforms.

ExoEdge provides functionality to interpret ExoSense data schemas and self-configure gateways. It is expected that the user will write a gateway application to leverage this library, but many use cases can be covered in very few lines of application code.

Requirements

  • Python 2.7.9+, 3.4, 3.5, 3.6,3.7

NOTE: In most cases, having a linux gateway is enough as long as the Python requirements are met. ExoEdge is pure-python, which means there is no need for gcc or python-dev in order to install and run it.

Getting started in 15m

Install ExoEdge

Make sure the gateway is connected to the internet before installing ExoEdge.

pip install exoedge

Start Reporting

Once ExoEdge is installed, it must be started with some variant of the following edged command (pronounced edge-dee, as in ‘edge daemon’).

edged -s <GATEWAY_SERIAL_#> -H <MURANO_PRODUCT_HOST> go

The edged command has many options (run edged --help for more information), but must be running in order for data to be reported to ExoSense.

Once edged is running, set the config_io resource to the object below in the Murano Product UI:

{
  "channels": {
    "one": {
      "display_name": "Temperature",
      "description": "It's the temperature",
      "properties": {
        "max": 1000,
        "data_unit": "DEG_CELSIUS",
        "precision": 4,
        "data_type": "TEMPERATURE",
        "min": 0
      },
      "protocol_config": {
        "application": "ExoSimulator",
        "report_on_change": false,
        "report_rate": 1000,
        "sample_rate": 1000,
        "down_sample": "ACT",
        "app_specific_config": {
          "function": "sin_wave",
          "module": "exo_simulator",
          "parameters": {
            "period": 120,
            "amplitude": 10,
            "offset": 100,
            "precision": 0,
          },
          "positionals": []
        }
      }
    }
  }
}

Verify that the device started reporting a sin wave with those characteristics.

For more information on the configuration options available in ExoEdge and ExoSense, please visit the ExoSense documentation.

Argument Support

The edged command supports supplying arguments via CLI flags, environment variables, and INI files.

Naming conventions differ slightly between these methods—for a generic argument some_argument they are named as follows:

  • CLI: --some-argument

  • Environment: EDGED_SOME_ARGUMENT

  • INI: some_argument

Argument Precedence

In handling conflicting arguments from different sources, edged evaluates arguments in the following order:

  1. CLI (overrides Environment and INI)

  2. Environent (overriden by CLI, overrides INI)

  3. INI (overriden by CLI and Environment)

Examples

With command line arguments and a local config_io:

$ edged --host=https://abcdef123456.m2.exosite.io/ -s device01.ini -c my_config.json -i device01.ini go

...^C

$ cat device01.ini
[device]
murano_token = DoHALdFD5Jo8Iz979Cc4ze5N6RlJCzbQbnkaP3Ci

With INI file:

$ cat device01.ini
[device]
murano_host = https://abcdef123456.m2.exosite.io/
murano_id = device01
watchlist = config_io
debug = INFO

$ edged -i device01.ini -c my_config.json go

NOTE: The murano_token option is not present in the .ini file prior to activation. If murano_token is present, the client will attempt to use that token, even if it’s blank, to communicate with Murano. The example, above, omits murano_token, because it will be saved there after edged provisions with Murano.

Local or Remote Configs

ExoEdge will always look for a local config_io file first, then check to see if there’s one in ExoSense. This can be overridden with the --strategy remote CLI option with will only ever use configs from ExoSense.

Creating an ExoEdge Source

There are two types of sources for data in ExoEdge: ones that are implemented and supported by Exosite (e.g. Modbus_TCP, Modbus_RTU, ExoSimulator, etc.), and ones that are provided by installed Python packages and modules.

Creating a “source” is as simple as writing Python package or module.

Simple Case

The “source”, above, illustrates that ExoEdge imports the my_source module and calls the function minutes_from_now with parameter 30 every second. This module needs to be installed like any other Python module or package (i.e. python setup.py install, pip install my_source, etc.).

# in my_source/__init__.py
import time

def minutes_to_seconds(min):
  return min * 60

def minutes_from_now(minutes=0):
  # Returns the timestamp `minutes` after the current time.
  return time.time() + minutes_to_seconds(minutes)
{
  "channels": {
    "001": {
      "display_name": "30 Minutes from Now",
      ...,
      "protocol_config": {
        "application": "Custom Application",
        "report_on_change": false,
        "report_rate": 1000,
        "sample_rate": 1000,
        "down_sample": "ACT",
        "app_specific_config": {
          "module": "my_source",
          "function": "minutes_from_now",
          "parameters": {
            "minutes": 30
          },
          "positionals": []
        }
        ...
      }
    }
  }
}

Complex Case

The example, below, utilizes the ExoEdgeSource class provided by ExoEdge.

NOTE: Though it is not required, when using the ExoEdgeSource class you can set protocol_config.application to ExoSimulator. Doing so will prevent an unecessary thread of execution from being started for the associated channel. By setting protocol_config.application to Custom Application or some other unique identifier, ExoEdge will import protocol_config.app_specific_config.module and call protocol_config.app_specific_config.function with protocol_config.app_specific_config.positionals and protocol_config.app_specific_config.parameters every protocol_config.sample_rate.

# my_source.py
import time
from exoedge.sources import ExoEdgeSource
from exoedge import logger

LOG = logger.getLogger(__name__)

def sixteen():
    return 16

class MySource(ExoEdgeSource):

    def seventeen(self):
        return 17

    def run(self):

        channels = self.get_channels_by_application('ExoSimulator')
        my_channels = []
        for channel in channels:
            if channel.protocol_config.app_specific_config['module'] == 'my_source':
                my_channels.append(channel)

        while True:

            for channel in my_channels:
                if channel.is_sample_time():
                    func = channel.protocol_config.app_specific_config['function']
                    if hasattr(sys.modules[__name__], func):
                        function = getattr(sys.modules[__name__], func)
                        par = channel.protocol_config.app_specific_config['parameters']
                        pos = channel.protocol_config.app_specific_config['positionals']
                        LOG.warning("calling '{}' with: **({})"
                                    .format(function, par))
                        try:
                            channel.put_sample(function(*pos, **par))
                        except Exception as exc: # pylint: disable=W0703
                            LOG.warning("Exception".format(format_exc=exc))
                            channel.put_channel_error(exc)
                    elif hasattr(self, func):
                        function = getattr(self, func)
                        par = channel.protocol_config.app_specific_config['parameters']
                        pos = channel.protocol_config.app_specific_config['positionals']
                        LOG.warning("calling '{}' with: **({})"
                                    .format(function, par))
                        try:
                            channel.put_sample(function(*pos, **par))
                        except Exception as exc: # pylint: disable=W0703
                            LOG.warning("Exception".format(format_exc=exc))
                            channel.put_channel_error(exc)
                    else:
                        channel.put_channel_error(
                            'MySource has no function: {}'.format(func))

            time.sleep(0.01) # throttle a bit to lay off the processor
{
  "channels": {
    "001": {
      "display_name": "My custom data source.",
      ...,
      "protocol_config": {
        "application": "ExoSimulator",
        "report_on_change": false,
        "report_rate": 1000,
        "sample_rate": 1000,
        "down_sample": "ACT",
        "app_specific_config": {
          "module": "my_source",
          "function": "minutes_from_now",
          "parameters": {
            "minutes": 30
          },
          "positionals": []
        }
        ...
      }
    }
  }
}

For more in-depth information on creating Python modules and packages, see the docs.

Passing Parameters

Some application functions take keyword arguments which are passed in the parameters section of the app_specific_config object in config_io. For instance, a function random_integer(lower=0, upper=10) which returns—you guessed it—a random integer between it’s lower and upper keyword arguments might have a parameters section like this:

"parameters": {
  "lower": 100,
  "upper": 200
}

Other application functions accept positional arguments. In this case, supply them in protocol_config.app_specific_config.positionals

"positionals": ["arg1", "foo", 15]

Upgrades

To upgrade ExoEdge, the following command should be used:

pip install exoedge --upgrade

Log Rotation

ExoEdge can be configured to save and rotate its own logs, as opposed to dumping logging to ‘stdout’.

# example
$ export EDGED_LOG_FILENAME=${PWD}/edged.log
$ edged -i f5330e5s8cho0000.ini go

Other supported environment variables for logging configuration are:

  • EDGED_LOG_DEBUG (default:CRITICAL)

  • EDGED_LOG_MAX_BYTES (default:1024000)

  • EDGED_LOG_MAX_BACKUPS (default:3)

You might wish to save and rotate the underlying murano_client logging as well. For help on this see murano-client.

A typical logging configuration for a production gateway looks like the following:

export EDGED_LOG_FILENAME=/var/log/edged.log
export MURANO_CLIENT_LOGFILE=/var/log/murano_client.log

A typical logging configuration for a development gateway might look like:

export EDGED_LOG_FILENAME=${PWD}/edged.log
export MURANO_CLIENT_LOGFILE=${PWD}/murano_client.log

TIP: If you don’t care about the murano_client logging, you can use the standard redirect since murano_client defaults to logging on ‘stderr’:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

exoedge-18.10.13-py2.py3-none-any.whl (36.9 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page