text: import logging
  import sys
  import time
  
  from pydnp3 import opendnp3, openpal, asiopal, asiodnp3
  from visitors import *
  
  FILTERS = opendnp3.levels.NORMAL | opendnp3.levels.ALL_COMMS
  HOST = "127.0.0.1"
  LOCAL = "0.0.0.0"
  PORT = 20000
  
  stdout_stream = logging.StreamHandler(sys.stdout)
  stdout_stream.setFormatter(logging.Formatter('%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s'))
  
  _log = logging.getLogger(__name__)
  _log.addHandler(stdout_stream)
  _log.setLevel(logging.DEBUG)
  
  
  class MyMaster:
      """
          Interface for all master application callback info except for measurement values.
  
          DNP3 spec section 5.1.6.1:
              The Application Layer provides the following services for the DNP3 User Layer in a master:
                  - Formats requests directed to one or more outstations.
                  - Notifies the DNP3 User Layer when new data or information arrives from an outstation.
  
          DNP spec section 5.1.6.3:
              The Application Layer requires specific services from the layers beneath it.
                  - Partitioning of fragments into smaller portions for transport reliability.
                  - Knowledge of which device(s) were the source of received messages.
                  - Transmission of messages to specific devices or to all devices.
                  - Message integrity (i.e., error-free reception and transmission of messages).
                  - Knowledge of the time when messages arrive.
                  - Either precise times of transmission or the ability to set time values
                    into outgoing messages.
      """
      def __init__(self,
                   log_handler=asiodnp3.ConsoleLogger().Create(),
                   listener=asiodnp3.PrintingChannelListener().Create(),
                   soe_handler=asiodnp3.PrintingSOEHandler().Create(),
                   master_application=asiodnp3.DefaultMasterApplication().Create(),
                   stack_config=None):
  
          _log.debug('Creating a DNP3Manager.')
          self.log_handler = log_handler
          self.manager = asiodnp3.DNP3Manager(1, self.log_handler)
  
          _log.debug('Creating the DNP3 channel, a TCP client.')
          self.retry = asiopal.ChannelRetry().Default()
          self.listener = listener
          self.channel = self.manager.AddTCPClient("tcpclient",
                                          FILTERS,
                                          self.retry,
                                          HOST,
                                          LOCAL,
                                          PORT,
                                          self.listener)
  
          _log.debug('Configuring the DNP3 stack.')
          self.stack_config = stack_config
          if not self.stack_config:
              self.stack_config = asiodnp3.MasterStackConfig()
              self.stack_config.master.responseTimeout = openpal.TimeDuration().Seconds(2)
              self.stack_config.link.RemoteAddr = 10
  
          _log.debug('Adding the master to the channel.')
          self.soe_handler = soe_handler
          self.master_application = master_application
          self.master = self.channel.AddMaster("master",
                                     asiodnp3.PrintingSOEHandler().Create(),
                                     self.master_application,
                                     self.stack_config)
  
          _log.debug('Configuring some scans (periodic reads).')
          # Set up a "slow scan", an infrequent integrity poll that requests events and static data for all classes.
          self.slow_scan = self.master.AddClassScan(opendnp3.ClassField().AllClasses(),
                                                    openpal.TimeDuration().Minutes(30),
                                                    opendnp3.TaskConfig().Default())
          # Set up a "fast scan", a relatively-frequent exception poll that requests events and class 1 static data.
          self.fast_scan = self.master.AddClassScan(opendnp3.ClassField(opendnp3.ClassField.CLASS_1),
                                                    openpal.TimeDuration().Minutes(1),
                                                    opendnp3.TaskConfig().Default())
  
          self.channel.SetLogFilters(openpal.LogFilters(opendnp3.levels.ALL_COMMS))
          self.master.SetLogFilters(openpal.LogFilters(opendnp3.levels.ALL_COMMS))
  
          _log.debug('Enabling the master. At this point, traffic will start to flow between the Master and Outstations.')
          self.master.Enable()
          time.sleep(5)
  
      def send_direct_operate_command(self, command, index, callback=asiodnp3.PrintingCommandCallback.Get(),
                                      config=opendnp3.TaskConfig().Default()):
          """
              Direct operate a single command
  
          :param command: command to operate
          :param index: index of the command
          :param callback: callback that will be invoked upon completion or failure
          :param config: optional configuration that controls normal callbacks and allows the user to be specified for SA
          """
          self.master.DirectOperate(command, index, callback, config)
  
      def send_direct_operate_command_set(self, command_set, callback=asiodnp3.PrintingCommandCallback.Get(),
                                          config=opendnp3.TaskConfig().Default()):
          """
              Direct operate a set of commands
  
          :param command_set: set of command headers
          :param callback: callback that will be invoked upon completion or failure
          :param config: optional configuration that controls normal callbacks and allows the user to be specified for SA
          """
          self.master.DirectOperate(command_set, callback, config)
  
      def send_select_and_operate_command(self, command, index, callback=asiodnp3.PrintingCommandCallback.Get(),
                                          config=opendnp3.TaskConfig().Default()):
          """
              Select and operate a single command
  
          :param command: command to operate
          :param index: index of the command
          :param callback: callback that will be invoked upon completion or failure
          :param config: optional configuration that controls normal callbacks and allows the user to be specified for SA
          """
          self.master.SelectAndOperate(command, index, callback, config)
  
      def send_select_and_operate_command_set(self, command_set, callback=asiodnp3.PrintingCommandCallback.Get(),
                                              config=opendnp3.TaskConfig().Default()):
          """
              Select and operate a set of commands
  
          :param command_set: set of command headers
          :param callback: callback that will be invoked upon completion or failure
          :param config: optional configuration that controls normal callbacks and allows the user to be specified for SA
          """
          self.master.SelectAndOperate(command_set, callback, config)
  
      def shutdown(self):
          del self.slow_scan
          del self.fast_scan
          del self.master
          del self.channel
          self.manager.Shutdown()
  
  
  class MyLogger(openpal.ILogHandler):
      """
          Override ILogHandler in this manner to implement application-specific logging behavior.
      """
  
      def __init__(self):
          super(MyLogger, self).__init__()
  
      def Log(self, entry):
          flag = opendnp3.LogFlagToString(entry.filters.GetBitfield())
          filters = entry.filters.GetBitfield()
          location = entry.location.rsplit('/')[-1] if entry.location else ''
          message = entry.message
          _log.debug('LOG\t\t{:<10}\tfilters={:<5}\tlocation={:<25}\tentry={}'.format(flag, filters, location, message))
  
  
  class AppChannelListener(asiodnp3.IChannelListener):
      """
          Override IChannelListener in this manner to implement application-specific channel behavior.
      """
  
      def __init__(self):
          super(AppChannelListener, self).__init__()
  
      def OnStateChange(self, state):
          _log.debug('In AppChannelListener.OnStateChange: state={}'.format(opendnp3.ChannelStateToString(state)))
  
  
  class SOEHandler(opendnp3.ISOEHandler):
      """
          Override ISOEHandler in this manner to implement application-specific sequence-of-events behavior.
  
          This is an interface for SequenceOfEvents (SOE) callbacks from the Master stack to the application layer.
      """
  
      def __init__(self):
          super(SOEHandler, self).__init__()
  
      def Process(self, info, values):
          """
              Process measurement data.
  
          :param info: HeaderInfo
          :param values: A collection of values received from the Outstation (various data types are possible).
          """
          visitor_class_types = {
              opendnp3.ICollectionIndexedBinary: VisitorIndexedBinary,
              opendnp3.ICollectionIndexedDoubleBitBinary: VisitorIndexedDoubleBitBinary,
              opendnp3.ICollectionIndexedCounter: VisitorIndexedCounter,
              opendnp3.ICollectionIndexedFrozenCounter: VisitorIndexedFrozenCounter,
              opendnp3.ICollectionIndexedAnalog: VisitorIndexedAnalog,
              opendnp3.ICollectionIndexedBinaryOutputStatus: VisitorIndexedBinaryOutputStatus,
              opendnp3.ICollectionIndexedAnalogOutputStatus: VisitorIndexedAnalogOutputStatus,
              opendnp3.ICollectionIndexedTimeAndInterval: VisitorIndexedTimeAndInterval
          }
          visitor_class = visitor_class_types[type(values)]
          visitor = visitor_class()
          values.Foreach(visitor)
          for index, value in visitor.index_and_value:
              log_string = 'SOEHandler.Process {0}\theaderIndex={1}\tdata_type={2}\tindex={3}\tvalue={4}'
              _log.debug(log_string.format(info.gv, info.headerIndex, type(values).__name__, index, value))
  
      def Start(self):
          _log.debug('In SOEHandler.Start')
  
      def End(self):
          _log.debug('In SOEHandler.End')
  
  
  class MasterApplication(opendnp3.IMasterApplication):
      def __init__(self):
          super(MasterApplication, self).__init__()
  
      # Overridden method
      def AssignClassDuringStartup(self):
          _log.debug('In MasterApplication.AssignClassDuringStartup')
          return False
  
      # Overridden method
      def OnClose(self):
          _log.debug('In MasterApplication.OnClose')
  
      # Overridden method
      def OnOpen(self):
          _log.debug('In MasterApplication.OnOpen')
  
      # Overridden method
      def OnReceiveIIN(self, iin):
          _log.debug('In MasterApplication.OnReceiveIIN')
  
      # Overridden method
      def OnTaskComplete(self, info):
          _log.debug('In MasterApplication.OnTaskComplete')
  
      # Overridden method
      def OnTaskStart(self, type, id):
          _log.debug('In MasterApplication.OnTaskStart')
  
  
  def collection_callback(result=None):
      """
      :type result: opendnp3.CommandPointResult
      """
      print("Header: {0} | Index:  {1} | State:  {2} | Status: {3}".format(
          result.headerIndex,
          result.index,
          opendnp3.CommandPointStateToString(result.state),
          opendnp3.CommandStatusToString(result.status)
      ))
  
  
  def command_callback(result=None):
      """
      :type result: opendnp3.ICommandTaskResult
      """
      print("Received command result with summary: {}".format(opendnp3.TaskCompletionToString(result.summary)))
      result.ForeachItem(collection_callback)
  
  
  def restart_callback(result=opendnp3.RestartOperationResult()):
      if result.summary == opendnp3.TaskCompletion.SUCCESS:
          print("Restart success | Restart Time: {}".format(result.restartTime.GetMilliseconds()))
      else:
          print("Restart fail | Failure: {}".format(opendnp3.TaskCompletionToString(result.summary)))
  
  
  def main():
      """The Master has been started from the command line. Execute ad-hoc tests if desired."""
      # app = MyMaster()
      app = MyMaster(log_handler=MyLogger(),
                     listener=AppChannelListener(),
                     soe_handler=SOEHandler(),
                     master_application=MasterApplication())
      _log.debug('Initialization complete. In command loop.')
      # Ad-hoc tests can be performed at this point. See master_cmd.py for examples.
      app.shutdown()
      _log.debug('Exiting.')
      exit()
  
  
  if __name__ == '__main__':
      main()
  