Package: openhistorian
The openHistorian Python API is used for high-speed reading and writing of time-series data with the openHistorian.The openHistorian is an open source system designed to efficiently integrate and archive process control data, e.g., SCADA, synchrophasor, digital fault recorder or any other time-series data used to support process operations. The openHistorian is optimized to store and retrieve large volumes of time-series data quickly and efficiently, including high-resolution sub-second information that is measured very rapidly, e.g., many thousands of times per second. See2-page pdf flyer.OverviewThe openHistorian 2 is built using theSNAPdb Engine- a key/value pair archiving technology. SNAPdb was developed to significantly improve the ability to handle extremely large volumes of real-time streaming data and directly serve the data to consuming applications and systems. See the Python API implementation ofSNAPdb.Through use of theSNAPdb Engine, the openHistorian inherits very fast performance with very low lag-time for data insertion. The openHistorian 2 is a time-series implementation of the SNABdb engine where the "key" is a tuple of time and measurement ID, and the "value" is the stored data - which can be most any data type and associated flags. See the Python API implementation of theopenHistorian instance of SNAPdbThe Python API for openHistorian is designed as a socket-based, high-speed API that interacts directly with the openHistorian in-memory cache for very high speed extraction of near real-time data. The archive files produced by the openHistorian areACID Compliantwhich create a very durable and consistent file structure that is resistant to data corruption. Internally the data structure is based on aB+ Treethat allows out-of-order data insertion.Example UsageFull source for the following two usage examples can be found here:Reading Data (readTest.py)Writing Data (writeTest.py)Reading DataThe following example shows how to establish a connection to the openHistorian, open a client database instance, refresh available metadata, filter metadata to desired set of signal types, establish a start and stop time for the read, then read each time-series values as a historiankey/valuepair.fromopenHistorian.historianConnectionimporthistorianConnectionfromopenHistorian.historianInstanceimporthistorianInstancefromopenHistorian.historianKeyimporthistorianKeyfromopenHistorian.historianValueimporthistorianValuefromopenHistorian.metadataCacheimportmetadataCachefromopenHistorian.measurementRecordimportSignalTypefromsnapDB.timestampSeekFilterimporttimestampSeekFilterfromsnapDB.pointIDMatchFilterimportpointIDMatchFilterfromtypingimportOptional,Listfromdatetimeimportdatetime,timedeltafromtimeimporttimeimportnumpyasnpdefreadTest():# Create historian connection (the root API object)historian=historianConnection("localhost")instance:Optional[historianInstance]=Nonetry:print("Connecting to openHistorian...")historian.Connect()ifnothistorian.IsConnectedorlen(historian.InstanceNames)==0:print("No openHistorian instances detected!")else:# Get first historian instanceinitialInstance=historian.InstanceNames[0]print(f"Opening\"{initialInstance}\"database instance...")instance=historian.OpenInstance(initialInstance)# Get a reference to the openHistorian metadata cachehistorian.RefreshMetadata()metadata=historian.Metadata# Lookup measurements that represent frequency valuesrecords=metadata.GetMeasurementsBySignalType(SignalType.FREQ,instance.Name)recordCount=len(records)print(f"Queried{recordCount:,}metadata records associated with\"{instance.Name}\"database instance.")ifrecordCount>0:pointIDList=metadataCache.ToPointIDList(records)# Execute a test read for data archived ten seconds agoendTime=datetime.utcnow()-timedelta(seconds=10)startTime=endTime-timedelta(milliseconds=33)print(f"Starting read for{len(pointIDList):,}points from{startTime}to{endTime}...\r\n")TestRead(instance,historian.Metadata,startTime,endTime,pointIDList)exceptExceptionasex:print(f"Failed to connect:{ex}")finally:ifinstanceisnotNone:instance.Dispose()ifhistorian.IsConnected:print("Disconnecting from openHistorian")historian.Disconnect()defTestRead(instance:historianInstance,metadata:metadataCache,startTime:datetime,endTime:datetime,pointIDList:List[np.uint64]):timeFilter=timestampSeekFilter.CreateFromRange(startTime,endTime)pointFilter=pointIDMatchFilter.CreateFromList(pointIDList)opStart=time()reader=instance.Read(timeFilter,pointFilter)count=0key=historianKey()value=historianValue()whilereader.Read(key,value):count+=1print(f"    Point{key.ToString(metadata)}={value.ToString()}")print(f"\r\nRead complete for{count:,}points in{(time()-opStart):.2f}seconds.\r\n")if__name__=="__main__":readTest()Writing dataThe following example shows how to establish a connection to the openHistorian, open a client database instance, build a new time-series value historiankey/valuepair, then write the data to the openHistorian.fromopenHistorian.historianConnectionimporthistorianConnectionfromopenHistorian.historianInstanceimporthistorianInstancefromopenHistorian.historianKeyimporthistorianKeyfromopenHistorian.historianValueimporthistorianValuefromsnapDB.enumerationsimportQualityFlagsfromgsfimportTicksfromtypingimportOptionalfromdatetimeimportdatetimeimportnumpyasnpdefwriteTest():# Create historian connection (the root API object)historian=historianConnection("localhost")instance:Optional[historianInstance]=Nonetry:print("Connecting to openHistorian...")historian.Connect()ifnothistorian.IsConnectedorlen(historian.InstanceNames)==0:print("No openHistorian instances detected!")else:# Get first historian instanceinitialInstance=historian.InstanceNames[0]print(f"Opening\"{initialInstance}\"database instance...")instance=historian.OpenInstance(initialInstance)key=historianKey()key.PointID=1key.Timestamp=Ticks.FromDateTime(datetime.utcnow())value=historianValue()value.AsSingle=np.float32(1000.98)value.AsQuality=QualityFlags.WARNINGHIGHprint("Writing test point...")instance.Write(key,value)exceptExceptionasex:print(f"Failed to connect:{ex}")finally:ifinstanceisnotNone:instance.Dispose()ifhistorian.IsConnected:print("Disconnecting from openHistorian")historian.Disconnect()if__name__=="__main__":writeTest()LicenseopenHistorian and the Python API are licensed under theMIT License.