i@rdZddlZddlZddlZddlZddlmZddlmZddlm Z dZ dZ dZ dZ eje e e e eje e e e d d gGd d ZGd dZGddeZGddeZedkr%ddlZejjedSdS)aDExcel IRTDServer implementation. This module is a functional example of how to implement the IRTDServer interface in python, using the pywin32 extensions. Further details, about this interface and it can be found at: https://learn.microsoft.com/en-us/previous-versions/office/developer/office-xp/aa140060(v=office.10) N) universal)gencache) COMExceptionz&{00020813-0000-0000-C000-000000000046} IRtdServerIRTDUpdateEventceZdZdZdgZgdZejZdZ dZ fdZ dZ dZ d Zd Zd Zd Zd ZddZdZdZdZdZdZxZS)ExcelRTDServeraBase RTDServer class. Provides most of the features needed to implement the IRtdServer interface. Manages topic adding, removal, and packing up the values for excel. Shouldn't be instanciated directly. Instead, descendant classes should override the CreateTopic() method. Topic objects only need to provide a GetValue() function to play nice here. The values given need to be atomic (eg. string, int, float... etc). Also note: nothing has been done within this class to ensure that we get time to check our topics for updates. I've left that up to the subclass since the ways, and needs, of refreshing your topics will vary greatly. For example, the sample implementation uses a timer thread to wake itself up. Whichever way you choose to do it, your class needs to be able to wake up occaisionally, since excel will never call your class without being asked to first. Excel will communicate with our object in this order: 1. Excel instanciates our object and calls ServerStart, providing us with an IRTDUpdateEvent callback object. 2. Excel calls ConnectData when it wants to subscribe to a new "topic". 3. When we have new data to provide, we call the UpdateNotify method of the callback object we were given. 4. Excel calls our RefreshData method, and receives a 2d SafeArray (row-major) containing the Topic ids in the 1st dim, and the topic values in the 2nd dim. 5. When not needed anymore, Excel will call our DisconnectData to unsubscribe from a topic. 6. When there are no more topics left, Excel will call our ServerTerminate method to kill us. Throughout, at undetermined periods, Excel will call our Heartbeat method to see if we're still alive. It must return a non-zero value, or we'll be killed. NOTE: By default, excel will at most call RefreshData once every 2 seconds. This is a setting that needs to be changed excel-side. To change this, you can set the throttle interval like this in the excel VBA object model: Application.RTD.ThrottleInterval = 1000 ' milliseconds r) ConnectDataDisconnectData Heartbeat RefreshData ServerStartServerTerminaterrc|t|j|_d|_i|_dS) ConstructorN)super__init__ALIVEIsAlive_ExcelRTDServer__callbacktopicsself __class__s KC:\PYTHON\_runtimes\venv\Lib\site-packages\win32com/demos/excelRTDServer.pyrzExcelRTDServer.__init__s4 z  cf|jtd|jdS)zCUse the callback we were given to tell excel new data is available.NzCallback excel provided is Nulldesc)rr UpdateNotifyrs r SignalExcelzExcelRTDServer.SignalExcels6 ? "$EFFF F $$&&&&&rc: |||j|<n/#t$r"}tt |d}~wwxYwd}|j|}|d|jjz}n|}||||fS)z6Creates a new topic out of the Strings excel gives us.r NTz# %s: Waiting for update) CreateTopicr Exceptionrstrr__name__GetValue OnConnectData)rTopicIDStrings GetNewValueswhyresults rr zExcelRTDServer.ConnectDatas .#'#3#3G#<#/$.2IIFF__&&F 7###|##s A AA cj||||jvrd|j|<|j|=dSdS)zDeletes the given topic.N)OnDisconnectDatarrr,s rr zExcelRTDServer.DisconnectDatasG g&&& dk ! !#'DK  G$$$ " !rc|jS)z+Called by excel to see if we're still here.)rr#s rrzExcelRTDServer.Heartbeats |rc>t|j}|dg|zdg|zg}t|jD]2\}}|\}}||d|<||d|<3t ||fS)aPacks up the topic values. Called by excel when it's ready for an update. Needs to: * Return the current number of topics, via the "ByRef" TopicCount * Return a 2d SafeArray of the topic data. - 1st dim: topic numbers - 2nd dim: topic values We could do some caching, instead of repacking everytime... But this works for demonstration purposes.Nrr)lenr OnRefreshData enumerateitemsr*tuple)r TopicCountresultsidx topicdatatopicNumtopics rrzExcelRTDServer.RefreshDatas%%  6J&(;<( (9(9(;(;<< / /NC'OHe&GAJsO#nn..GAJsOOW~~z))rc|j|_|tdtjjd}|||_||jS)zMExcel has just created us... We take its callback for later, and set up shop.Nz Excel did not provide a callbackr z&{A43788C1-D91B-11D3-8F39-00C04F3651B8}) rrrwin32comclient CLSIDToClassGetClassr OnServerStart)rCallbackObjectIRTDUpdateEventKlasss rrzExcelRTDServer.ServerStartsqz  !$FGGG G (;DD 4  /.~>> |rcF|j|_|dS)z%Called when excel no longer wants us.N) NOT_ALIVErOnServerTerminater#s rrzExcelRTDServer.ServerTerminates#~       rNc td)zTopic factory method. Subclass must override. Topic objects need to provide: * GetValue() method which returns an atomic value. Will raise NotImplemented if not overridden. zSubclass must implementNotImplementedr TopicStringss rr&zExcelRTDServer.CreateTopics6777rcdS)z=Called when a new topic has been created, at excel's request.Nr3s rr+zExcelRTDServer.OnConnectData rcdS)z?Called when a topic is about to be deleted, at excel's request.NrRr3s rr2zExcelRTDServer.OnDisconnectDatarSrcdS)z7Called when excel has requested all current topic data.NrRr#s rr7zExcelRTDServer.OnRefreshDatarSrcdS)z&Called when excel has instanciated us.NrRr#s rrFzExcelRTDServer.OnServerStartrSrcdS)z)Called when excel is about to destroy us.NrRr#s rrKz ExcelRTDServer.OnServerTerminaterSrN)r) __module__ __qualname____doc___com_interfaces__public_methods_ pythoncomCLSCTX_INPROC_SERVER _reg_clsctx_rrJrr$r r rrrrr&r+r2r7rFrK __classcell__rs@rr r Hs>))V%~1L EI''' $$$&%%%***@"!!! 8888                   rr c@eZdZdZfdZdZdZdZdZdZ xZ S)RTDTopiczBase RTD Topic. Only method required by our RTDServer implementation is GetValue(). The others are more for convenience.crt||_d|_d|_dS)NF)rrrP_RTDTopic__currentValue_RTDTopic__dirty)rrPrs rrzRTDTopic.__init__ s4 (" rc td)zCalled by the RTD Server. Gives us a chance to check if our topic data needs to be changed (eg. check a file, quiz a database, etc).zsubclass must implementrM)rsenders rUpdatezRTDTopic.Updates6777rcd|_dS)z6Call when this topic isn't considered "dirty" anymore.FNrgr#s rResetzRTDTopic.Resets  rc|jSrX)rfr#s rr*zRTDTopic.GetValues ""rc"d|_||_dS)NT)rgrf)rvalues rSetValuezRTDTopic.SetValues #rc|jSrXrlr#s r HasChangedzRTDTopic.HasChanged#s |r) r)rYrZr[rrjrmr*rqrsrarbs@rrdrds,, 888 ###$$$rrdcLeZdZdZdZdZdZdZfdZdZ dZ d Z d d Z xZ S) TimeServeraDExample Time RTD server. Sends time updates back to excel. example of use, in an excel sheet: =RTD("Python.RTD.TimeServer","","seconds","5") This will cause a timestamp string to fill the cell, and update its value every 5 seconds (or as close as possible depending on how busy excel is). The empty string parameter denotes the com server is running on the local machine. Otherwise, put in the hostname to look on. For more info on this, lookup the Excel help for its "RTD" worksheet function. Obviously, you'd want to wrap this kind of thing in a friendlier VBA function. Also, remember that the RTD function accepts a maximum of 28 arguments! If you want to pass more, you may need to concatenate arguments into one string, and have your topic parse them appropriately. z&{EA7F2CF1-11A2-45E4-B2D5-68E240DB8CB1}zPython.RTD.TimeServerz8Python class implementing Excel IRTDServer -- feeds timeg?cttj|j|j|_dSrX)rr threadingTimerINTERVALrjtickerrs rrzTimeServer.__init__Ns6  odmT[AA rc8|jdSrX)rzstartr#s rrFzTimeServer.OnServerStartYs rcx|jjs|jdSdSrX)rzfinishedis_setcancelr#s rrKzTimeServer.OnServerTerminate\s?{#**,, ! K    ! !rctj|j|j|_ t |jrsd}|jD]A}|||rd}| B|r| |j dS#|j wxYw)NFT) rwrxryrjrzr6rvaluesrsrmr$r|)rrefreshr@s rrjzTimeServer.Update`sodmT[AA  4; '![//11""ELL&&&''))'"&KKMMMM'$$&&& K       DK      s BCC#Nc t|S)zGTopic factory. Builds a TimeTopic object out of the given TopicStrings.) TimeTopicrOs rr&zTimeServer.CreateTopicrs&&&rrX)r)rYrZr[ _reg_clsid_ _reg_progid_ _reg_desc_ryrrFrKrjr&rarbs@rruru.s0;K*LKJH B B B B B!!!   $''''''''rruc.eZdZdZfdZdZdZxZS)rzExample topic for example RTD server. Will accept some simple commands to alter how long to delay value updates. Commands: * seconds, delay_in_seconds * minutes, delay_in_minutes * hours, delay_in_hours ct| |j\|_|_n1#t $r$}t dt|zd}~wwxYwt|j|_| |_ | t|j dS)NzInvalid topic strings: %s) rrrPcmddelayr' ValueErrorr(float timestamp checkpointrq)rrPErs rrzTimeTopic.__init__s &&& N#'#4 DHdjj N N N83|;L;LLMM M  N4:&& ..** c$/**+++++s9 A'A""A'c>tjSrX)datetimenowr#s rrzTimeTopic.timestamps $$&&&rc|}||jz }d}|jdkr|j|jkrd}nY|jdkr|j|jkrd}n;|jdkr|j|jkrd}n|d|jz|r+|t|||_dSdS)NFsecondsTminuteshoursz#Unknown command: ) rrrrrrrrqr()rrirdeltars rrjzTimeTopic.Updatesnndo% 8y } ** X " "} ** X {dj(( MM.9 : : :  " MM#c(( # # #!DOOO " "r)r)rYrZr[rrrjrarbs@rrrws`,,,,,"'''"""""""rr__main__)r[rrwr^win32com.clientrBrrwin32com.server.exceptionrEXCEL_TLB_GUIDEXCEL_TLB_LCIDEXCEL_TLB_MAJOREXCEL_TLB_MINOR EnsureModuleRegisterInterfacesr rdrurr)win32com.server.registerserverregisterUseCommandLinerRrrrs@$$$$$$222222 :nnoWWW $% | | | | | | | | ~NF'F'F'F'F'F'F'F'R1"1"1"1"1"1"1"1"h z####  O++J77777r