diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..0d20b6487c61e7d1bde93acf4a14b7a89083a16d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/npyscreenreactor.py b/npyscreenreactor.py new file mode 100644 index 0000000000000000000000000000000000000000..b7931cd06ca502b3fb67114f82d1ff00564eed7d --- /dev/null +++ b/npyscreenreactor.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +# npyscreenreactory.py + +# Inspired by pausingreactor.py and xmmsreactor.py + +# npyscreen modifications +# Copyright (c) 2015 Mark Tearle <mark@tearle.com> + +""" +This module provides wxPython event loop support for Twisted. + +In order to use this support, simply do the following:: + + | import npyscreenreactor + | npyscreenreactor.install() + +Then, when your root npyscreenApp has been created:: + + | from twisted.internet import reactor + | reactor.registerNpyscreenApp(yourApp) + | reactor.run() + +Then use twisted.internet APIs as usual. Stop the event loop using +reactor.stop(), not yourApp.ExitMainLoop(). + +IMPORTANT: tests will fail when run under this reactor. This is +expected and probably does not reflect on the reactor's ability to run +real applications. + +Maintainer: Mark Tearle +""" + +import sys + +import Queue + +from twisted.python import log, runtime +#from twisted.internet import _threadedselect +from twisted.internet import selectreactor + +import npyscreen + +#class NpyscreenReactor(_threadedselect.ThreadedSelectReactor): +class NpyscreenReactor(selectreactor.SelectReactor): + """ + npyscreen reactor. + + npyscreen drives the event loop + """ + def doIteration(self, timeout): + selectreactor.SelectReactor.doIteration(self, timeout) + + # push event back on npyscreen queue + self.npyscreenapp.queue_event(npyscreen.Event("_NPYSCREEN_REACTOR")) + + def registerNpyscreenApp(self, npyscreenapp): + """ + Register npyscreen.StandardApp instance with the reactor. + """ + self.npyscreenapp = npyscreenapp + self.npyscreenapp.add_event_hander("_NPYSCREEN_REACTOR", self._twisted_events) + + def _twisted_events(self, event): + self.doIteration(0) + + def _stopNpyscreen(self): + """ + Stop the Npsycreen event loop if it hasn't already been stopped. + + Called during Twisted event loop shutdown. + """ + if hasattr(self, "npyscreenapp"): + self.npyscreenapp.setNextForm(None) + + def run(self): + """ + Start the reactor. + """ + # add cleanup events: + self.addSystemEventTrigger("after", "shutdown", self._stopNpyscreen) + + # put event on queue to do twisted things + self.npyscreenapp.queue_event(npyscreen.Event("_NPYSCREEN_REACTOR")) + + # + self.npyscreenapp.run() + +def install(): + """ + Configure the twisted mainloop to be run inside the npyscreen mainloop. + """ + reactor = NpyscreenReactor() + from twisted.internet.main import installReactor + installReactor(reactor) + return reactor + + +__all__ = ['install'] diff --git a/virtualcoke.py b/virtualcoke.py index e680019c3711ed909b6de5e2b16c397316b39ed1..0de90e80f0dbaa326b677f3328fd5baa251014c5 100755 --- a/virtualcoke.py +++ b/virtualcoke.py @@ -2,13 +2,20 @@ import npyscreen from datetime import datetime +# npyscreen twisted reactor +import npyscreenreactor +reactor = npyscreenreactor.install() + # Incorporates code from #Pymodbus Asynchronous Server Example #---------------------------------------------------------------------------# # import the various server implementations #---------------------------------------------------------------------------# -from pymodbus.server.async import StartTcpServer +from pymodbus.constants import Defaults +from pymodbus.transaction import ModbusSocketFramer, ModbusAsciiFramer +from pymodbus.server.async import ModbusServerFactory + from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext @@ -31,6 +38,28 @@ logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) +# Factory for modbus + +def StartModbusAsyncServer(context, identity=None, address=None, console=False): + ''' Helper method to start the Modbus Async TCP server + + :param context: The server data context + :param identify: The server identity to use (default empty) + :param address: An optional (interface, port) to bind to. + :param console: A flag indicating if you want the debug console + ''' + from twisted.internet import reactor + + address = address or ("", Defaults.Port) + framer = ModbusSocketFramer + factory = ModbusServerFactory(context, framer, identity) + if console: InstallManagementConsole({'factory': factory}) + + log.info("Starting Modbus TCP Server on %s:%s" % address) + reactor.listenTCP(address[1], factory, interface=address[0]) + + +# class ContainedMultiSelect(npyscreen.BoxTitle): _contained_widget = npyscreen.TitleMultiSelect @@ -148,7 +177,7 @@ class VirtualCoke(npyscreen.Form): self.editing = False -class VirtualCokeApp(npyscreen.NPSAppManaged): +class VirtualCokeApp(npyscreen.StandardApp): keypress_timeout_default = 1 def onStart(self): @@ -200,100 +229,99 @@ class VirtualCokeApp(npyscreen.NPSAppManaged): # Coke Emulator code below -if __name__ == "__main__": - App = VirtualCokeApp() - App.run() - - -##---------------------------------------------------------------------------# -## initialize your data store -##---------------------------------------------------------------------------# -## The datastores only respond to the addresses that they are initialized to. -## Therefore, if you initialize a DataBlock to addresses of 0x00 to 0xFF, a -## request to 0x100 will respond with an invalid address exception. This is -## because many devices exhibit this kind of behavior (but not all):: -## -## block = ModbusSequentialDataBlock(0x00, [0]*0xff) -## -## Continuting, you can choose to use a sequential or a sparse DataBlock in -## your data context. The difference is that the sequential has no gaps in -## the data while the sparse can. Once again, there are devices that exhibit -## both forms of behavior:: -## -## block = ModbusSparseDataBlock({0x00: 0, 0x05: 1}) -## block = ModbusSequentialDataBlock(0x00, [0]*5) -## -## Alternately, you can use the factory methods to initialize the DataBlocks -## or simply do not pass them to have them initialized to 0x00 on the full -## address range:: -## -## store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create()) -## store = ModbusSlaveContext() -## -## Finally, you are allowed to use the same DataBlock reference for every -## table or you you may use a seperate DataBlock for each table. This depends -## if you would like functions to be able to access and modify the same data -## or not:: -## -## block = ModbusSequentialDataBlock(0x00, [0]*0xff) -## store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) -##---------------------------------------------------------------------------# -# -##---------------------------------------------------------------------------# -## create your custom data block with callbacks -##---------------------------------------------------------------------------# -#class CallbackDataBlock(ModbusSparseDataBlock): -# ''' A datablock that stores the new value in memory -# and passes the operation to a message queue for further -# processing. -# ''' -# -# def __init__(self, values): -# self.toggle = 1 -# super(CallbackDataBlock, self).__init__(values) -# -# -# def getValues(self, address, count=1): -# ''' Returns the requested values from the datastore -# -# :param address: The starting address -# :param count: The number of values to retrieve -# :returns: The requested values from a:a+c -# ''' -# log.debug("CBD getValues %d:%d" % (address, count)) -# -# if address < 1024: -# return [1] +#---------------------------------------------------------------------------# +# initialize your data store +#---------------------------------------------------------------------------# +# The datastores only respond to the addresses that they are initialized to. +# Therefore, if you initialize a DataBlock to addresses of 0x00 to 0xFF, a +# request to 0x100 will respond with an invalid address exception. This is +# because many devices exhibit this kind of behavior (but not all):: # -# self.toggle = self.toggle+1 +# block = ModbusSequentialDataBlock(0x00, [0]*0xff) # -# log.debug("CBD getValues toggle %d" % (self.toggle)) -# if self.toggle % 3 == 0: -# return [1] -# -# if self.toggle % 3 == 1: -# return [1] +# Continuting, you can choose to use a sequential or a sparse DataBlock in +# your data context. The difference is that the sequential has no gaps in +# the data while the sparse can. Once again, there are devices that exhibit +# both forms of behavior:: # -# if self.toggle % 3 == 2: -# return [0] -# +# block = ModbusSparseDataBlock({0x00: 0, 0x05: 1}) +# block = ModbusSequentialDataBlock(0x00, [0]*5) # -# return [1] -# +# Alternately, you can use the factory methods to initialize the DataBlocks +# or simply do not pass them to have them initialized to 0x00 on the full +# address range:: # +# store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create()) +# store = ModbusSlaveContext() # -#store = ModbusSlaveContext( -# #di = ModbusSequentialDataBlock(0, [17]*100), -# #co = ModbusSequentialDataBlock(0, [17]*100), -# #hr = ModbusSequentialDataBlock(0, [17]*100), -# #ir = ModbusSequentialDataBlock(0, [17]*100)) -# di = CallbackDataBlock([0]*100), -# co = CallbackDataBlock([0]*65536), -# hr = CallbackDataBlock([0]*100), -# ir = CallbackDataBlock([0]*100)) -#context = ModbusServerContext(slaves=store, single=True) +# Finally, you are allowed to use the same DataBlock reference for every +# table or you you may use a seperate DataBlock for each table. This depends +# if you would like functions to be able to access and modify the same data +# or not:: # -##---------------------------------------------------------------------------# -## run the server you want -##---------------------------------------------------------------------------# -#StartTcpServer(context) +# block = ModbusSequentialDataBlock(0x00, [0]*0xff) +# store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) +#---------------------------------------------------------------------------# + +#---------------------------------------------------------------------------# +# create your custom data block with callbacks +#---------------------------------------------------------------------------# +class CallbackDataBlock(ModbusSparseDataBlock): + ''' A datablock that stores the new value in memory + and passes the operation to a message queue for further + processing. + ''' + + def __init__(self, values): + self.toggle = 1 + super(CallbackDataBlock, self).__init__(values) + + + def getValues(self, address, count=1): + ''' Returns the requested values from the datastore + + :param address: The starting address + :param count: The number of values to retrieve + :returns: The requested values from a:a+c + ''' + log.debug("CBD getValues %d:%d" % (address, count)) + + if address < 1024: + return [1] + + self.toggle = self.toggle+1 + + log.debug("CBD getValues toggle %d" % (self.toggle)) + if self.toggle % 3 == 0: + return [1] + + if self.toggle % 3 == 1: + return [1] + + if self.toggle % 3 == 2: + return [0] + + + return [1] + + + +store = ModbusSlaveContext( + #di = ModbusSequentialDataBlock(0, [17]*100), + #co = ModbusSequentialDataBlock(0, [17]*100), + #hr = ModbusSequentialDataBlock(0, [17]*100), + #ir = ModbusSequentialDataBlock(0, [17]*100)) + di = CallbackDataBlock([0]*100), + co = CallbackDataBlock([0]*65536), + hr = CallbackDataBlock([0]*100), + ir = CallbackDataBlock([0]*100)) + +context = ModbusServerContext(slaves=store, single=True) + + +if __name__ == "__main__": + App = VirtualCokeApp() + reactor.registerNpyscreenApp(App) + StartModbusAsyncServer(context) + reactor.run() +