1
- from threading import RLock
2
1
import logging
3
2
from datetime import datetime
4
- import collections
5
- import shelve
6
- try :
7
- import cPickle as pickle
8
- except :
9
- import pickle
10
3
11
4
from opcua import ua
12
5
from opcua .server .user_manager import UserManager
6
+ from opcua .common .utils import ThreadSafeDict
13
7
14
8
15
9
class AttributeValue (object ):
@@ -478,51 +472,30 @@ def _call(self, method):
478
472
return res
479
473
480
474
481
- class AddressSpace (object ):
475
+ class AddressSpace (ThreadSafeDict ):
482
476
483
477
"""
484
478
The address space object stores all the nodes of the OPC-UA server
485
479
and helper methods.
486
480
The methods are thread safe
487
481
"""
488
482
489
- def __init__ (self ):
483
+ def __init__ (self , cache = None ):
484
+ super (AddressSpace , self ).__init__ (cache )
490
485
self .logger = logging .getLogger (__name__ )
491
- self ._nodes = {}
492
- self ._lock = RLock () # FIXME: should use multiple reader, one writter pattern
493
486
self ._datachange_callback_counter = 200
494
487
self ._handle_to_attribute_map = {}
495
488
self ._default_idx = 2
496
489
self ._nodeid_counter = {0 : 20000 , 1 : 2000 }
497
490
498
- def __getitem__ (self , nodeid ):
499
- with self ._lock :
500
- return self ._nodes .__getitem__ (nodeid )
501
-
502
- def get (self , nodeid ):
503
- with self ._lock :
504
- return self ._nodes .get (nodeid , None )
505
-
506
- def __setitem__ (self , nodeid , value ):
507
- with self ._lock :
508
- return self ._nodes .__setitem__ (nodeid , value )
509
-
510
- def __contains__ (self , nodeid ):
511
- with self ._lock :
512
- return self ._nodes .__contains__ (nodeid )
513
-
514
- def __delitem__ (self , nodeid ):
515
- with self ._lock :
516
- self ._nodes .__delitem__ (nodeid )
517
-
518
491
def generate_nodeid (self , idx = None ):
519
492
if idx is None :
520
493
idx = self ._default_idx
521
494
if idx in self ._nodeid_counter :
522
495
self ._nodeid_counter [idx ] += 1
523
496
else :
524
497
# get the biggest identifier number from the existed nodes in address space
525
- identifier_list = sorted ([nodeid .Identifier for nodeid in self ._nodes . keys ()
498
+ identifier_list = sorted ([nodeid .Identifier for nodeid in self .keys ()
526
499
if nodeid .NamespaceIndex == idx and nodeid .NodeIdType
527
500
in (ua .NodeIdType .Numeric , ua .NodeIdType .TwoByte , ua .NodeIdType .FourByte )])
528
501
if identifier_list :
@@ -532,111 +505,19 @@ def generate_nodeid(self, idx=None):
532
505
nodeid = ua .NodeId (self ._nodeid_counter [idx ], idx )
533
506
with self ._lock : # OK since reentrant lock
534
507
while True :
535
- if nodeid in self ._nodes :
508
+ if nodeid in self .keys () :
536
509
nodeid = self .generate_nodeid (idx )
537
510
else :
538
511
return nodeid
539
512
540
- def keys (self ):
541
- with self ._lock :
542
- return self ._nodes .keys ()
543
-
544
- def empty (self ):
545
- """
546
- Delete all nodes in address space
547
- """
548
- with self ._lock :
549
- self ._nodes = {}
550
-
551
- def dump (self , path ):
552
- """
553
- Dump address space as binary to file; note that server must be stopped for this method to work
554
- DO NOT DUMP AN ADDRESS SPACE WHICH IS USING A SHELF (load_aspace_shelf), ONLY CACHED NODES WILL GET DUMPED!
555
- """
556
- # prepare nodes in address space for being serialized
557
- for nodeid , ndata in self ._nodes .items ():
558
- # if the node has a reference to a method call, remove it so the object can be serialized
559
- if ndata .call is not None :
560
- self ._nodes [nodeid ].call = None
561
-
562
- with open (path , 'wb' ) as f :
563
- pickle .dump (self ._nodes , f , pickle .HIGHEST_PROTOCOL )
564
-
565
- def load (self , path ):
566
- """
567
- Load address space from a binary file, overwriting everything in the current address space
568
- """
569
- with open (path , 'rb' ) as f :
570
- self ._nodes = pickle .load (f )
571
-
572
- def make_aspace_shelf (self , path ):
573
- """
574
- Make a shelf for containing the nodes from the standard address space; this is typically only done on first
575
- start of the server. Subsequent server starts will load the shelf, nodes are then moved to a cache
576
- by the LazyLoadingDict class when they are accessed. Saving data back to the shelf
577
- is currently NOT supported, it is only used for the default OPC UA standard address space
578
-
579
- Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time
580
- """
581
- s = shelve .open (path , "n" , protocol = pickle .HIGHEST_PROTOCOL )
582
- for nodeid , ndata in self ._nodes .items ():
583
- s [nodeid .to_string ()] = ndata
584
- s .close ()
585
-
586
- def load_aspace_shelf (self , path ):
587
- """
588
- Load the standard address space nodes from a python shelve via LazyLoadingDict as needed.
589
- The dump() method can no longer be used if the address space is being loaded from a shelf
590
-
591
- Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time
592
- """
593
- class LazyLoadingDict (collections .MutableMapping ):
594
- """
595
- Special dict that only loads nodes as they are accessed. If a node is accessed it gets copied from the
596
- shelve to the cache dict. All user nodes are saved in the cache ONLY. Saving data back to the shelf
597
- is currently NOT supported
598
- """
599
- def __init__ (self , source ):
600
- self .source = source # python shelf
601
- self .cache = {} # internal dict
602
-
603
- def __getitem__ (self , key ):
604
- # try to get the item (node) from the cache, if it isn't there get it from the shelf
605
- try :
606
- return self .cache [key ]
607
- except KeyError :
608
- node = self .cache [key ] = self .source [key .to_string ()]
609
- return node
610
-
611
- def __setitem__ (self , key , value ):
612
- # add a new item to the cache; if this item is in the shelf it is not updated
613
- self .cache [key ] = value
614
-
615
- def __contains__ (self , key ):
616
- return key in self .cache or key .to_string () in self .source
617
-
618
- def __delitem__ (self , key ):
619
- # only deleting items from the cache is allowed
620
- del self .cache [key ]
621
-
622
- def __iter__ (self ):
623
- # only the cache can be iterated over
624
- return iter (self .cache .keys ())
625
-
626
- def __len__ (self ):
627
- # only returns the length of items in the cache, not unaccessed items in the shelf
628
- return len (self .cache )
629
-
630
- self ._nodes = LazyLoadingDict (shelve .open (path , "r" ))
631
-
632
513
def get_attribute_value (self , nodeid , attr ):
633
514
with self ._lock :
634
515
self .logger .debug ("get attr val: %s %s" , nodeid , attr )
635
- if nodeid not in self ._nodes :
516
+ if nodeid not in self .keys () :
636
517
dv = ua .DataValue ()
637
518
dv .StatusCode = ua .StatusCode (ua .StatusCodes .BadNodeIdUnknown )
638
519
return dv
639
- node = self . _nodes [nodeid ]
520
+ node = self [nodeid ]
640
521
if attr not in node .attributes :
641
522
dv = ua .DataValue ()
642
523
dv .StatusCode = ua .StatusCode (ua .StatusCodes .BadAttributeIdInvalid )
@@ -649,7 +530,7 @@ def get_attribute_value(self, nodeid, attr):
649
530
def set_attribute_value (self , nodeid , attr , value ):
650
531
with self ._lock :
651
532
self .logger .debug ("set attr val: %s %s %s" , nodeid , attr , value )
652
- node = self ._nodes . get (nodeid , None )
533
+ node = self .get (nodeid , None )
653
534
if node is None :
654
535
return ua .StatusCode (ua .StatusCodes .BadNodeIdUnknown )
655
536
attval = node .attributes .get (attr , None )
@@ -673,9 +554,9 @@ def set_attribute_value(self, nodeid, attr, value):
673
554
def add_datachange_callback (self , nodeid , attr , callback ):
674
555
with self ._lock :
675
556
self .logger .debug ("set attr callback: %s %s %s" , nodeid , attr , callback )
676
- if nodeid not in self ._nodes :
557
+ if nodeid not in self .keys () :
677
558
return ua .StatusCode (ua .StatusCodes .BadNodeIdUnknown ), 0
678
- node = self . _nodes [nodeid ]
559
+ node = self [nodeid ]
679
560
if attr not in node .attributes :
680
561
return ua .StatusCode (ua .StatusCodes .BadAttributeIdInvalid ), 0
681
562
attval = node .attributes [attr ]
@@ -689,9 +570,9 @@ def delete_datachange_callback(self, handle):
689
570
with self ._lock :
690
571
if handle in self ._handle_to_attribute_map :
691
572
nodeid , attr = self ._handle_to_attribute_map .pop (handle )
692
- self . _nodes [nodeid ].attributes [attr ].datachange_callbacks .pop (handle )
573
+ self [nodeid ].attributes [attr ].datachange_callbacks .pop (handle )
693
574
694
575
def add_method_callback (self , methodid , callback ):
695
576
with self ._lock :
696
- node = self . _nodes [methodid ]
577
+ node = self [methodid ]
697
578
node .call = callback
0 commit comments