QUESTION: Is the following approach to object persistence in Ignition optimal?
(I added a lot of extra documentation, because I think this is probably a common-ish problem in manufacturing.)
I have a VB.NET program that sits on a test station and talks to a few devices. This was legacy code that was reworked a little to work with Ignition system.
We talk to the VB program and get data back from it through a PLC middleman.
I want to move this VB.NET driver into Ignition, converting to Python 2.5.
Why I want to make the change:
- One language for future developers - Python
- Reduce pain of adding / rearranging stations, by not relying on PLC unnecessarily
- More maintainable
- Far richer controls from user’s perspective ( a lot of features have not been added, because of limited PLC space and pain of deployment ).
- Cheaper -> No PLCs for at least this part of our system
Characteristics of driver:
- receives async command from user
- maintains state & periodically polls the device to keep connection alive
- periodically does other things like evaluate bus to check for new devices
- only one command allowed at time to be sent to device, to avoid weird overlapping commands from separate chains of commands that result in funky operation.
The Approach I am Thinking of:
create a class, Device() -> see below for example. Use Python 3…just for print statements
use a few kill events - Event() thread objects - to stop long threaded processes, like testing and polling
set these kill events when I need to abruptly end something in a safe manner ( the real-world version will involve data collection with a Queue() )
- use putClientProperty/getClientProperty to store this class between asynchronous calls to action from the user to test / abort a device
import threading, time
def __init__(self): self._poll_kill_event = threading.Event() self._kill_event = threading.Event() self._lock = threading.Lock() self.__poll() self._stopped = False def __send(self,cmd): print( 'sending %s to device...' % cmd.upper(), end='' ) time.sleep(1) print( 'done!' ) def __poll(self): def poll(): while True: try: self._lock.acquire() self.__send('polling...') finally: self._lock.release() time.sleep(2) if self._poll_kill_event.isSet(): print('polling kill event received!') self._poll_kill_event.clear() break t=threading.Thread(target=poll) t.daemon=True t.start() def stop(self): print('stopping device...',end='') if self._stopped == False: self._poll_kill_event.set() while self._poll_kill_event.isSet(): time.sleep(1) self._stopped = True print('done!') def _test(self): try: self._lock.acquire() start = time.process_time() while time.process_time() - start < 10: self.__send( 'testing...' ) if self._kill_event.isSet(): self._kill_event.clear() print('kill event received. stop sending TEST cmd.') break finally: self._lock.release() def test(self): t=threading.Thread(target=self._test) t.daemon=True t.start() def abort(self): if self._lock.locked(): self._kill_event.set() print('waiting for lock...',end='') while self._lock.locked(): time.sleep(1) print('done!') try: self._lock.acquire() self.__send( 'abort' ) finally: self._lock.release()
device = Device()
So, again my question is whether this is the best approach to this problem.
I haven’t seen anything quite like this question here. Most questions about persistence revolved around global variables or using the database and tags, but that is painful while an object-oriented approach feels much cleaner and understandable.
I am open to hearing other possible implementation schemes…whatever anyone has to offer.