├── __init__.py ├── lib ├── __init__.py └── ddeclient.py ├── README.md ├── setup.py ├── LICENSE ├── rakuten_rss.py ├── .gitignore └── docs ├── calc_sq_2019feb01_weekly.ipynb └── test_rakten_rss.ipynb /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rakuten_rss 2 | Python module for Rakuten RSS 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from codecs import open 2 | 3 | from setuptools import setup, find_packages 4 | 5 | with open("README.md", "r", encoding="utf-8") as fh: 6 | long_description = fh.read() 7 | 8 | setup( 9 | name='rakuten_rss', 10 | version='0.0.1', 11 | description='Python module for Rakuten RSS', 12 | long_description=long_description, 13 | url='https://github.com/zaq9/rakuten_rss', 14 | author='zaq', 15 | author_email='zaq_9@yahoo.co.jp', 16 | 17 | classifiers=[ 18 | # How mature is this project? Common values are 19 | # 3 - Alpha 20 | # 4 - Beta 21 | # 5 - Production/Stable 22 | 'Development Status :: 3 - Alpha', 23 | 'Programming Language :: Python :: 3', 24 | ], 25 | keywords=['python', 'finance', 'MarketData', 'RakutenRSS'], 26 | # py_modules=['nk225op'], 27 | license='MIT', 28 | packages=find_packages(exclude=('docs')), 29 | install_requires=[], 30 | ) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zaq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rakuten_rss.py: -------------------------------------------------------------------------------- 1 | """楽天RSS用モジュール 2 | """ 3 | from lib.ddeclient import DDEClient 4 | 5 | 6 | def rss(code, item): 7 | """ 楽天RSSから情報を取得 8 | Parameters 9 | ---------- 10 | code : str 11 | 株価や先物のコード 例:東京電力の場合'9501.T' 12 | item : 13 | Returns 14 | ------- 15 | str 16 | 17 | Examples 18 | ---------- 19 | 20 | >>>rss('9501.T' , '始値') 21 | '668.00' 22 | 23 | >>>rss('9501.T' , '現在値') 24 | '669.00' 25 | 26 | >>>rss('9501.T' , '銘柄名称') 27 | '東京電力HD' 28 | 29 | >>>rss('9501.T' , '現在値詳細時刻') 30 | '15:00:00' 31 | 32 | """ 33 | 34 | dde = DDEClient("rss", str(code)) 35 | try: 36 | res = dde.request(item).decode('sjis').strip() 37 | except: 38 | print('fail: code@', code) 39 | res = 0 40 | finally: 41 | dde.__del__() 42 | return res 43 | 44 | 45 | def rss_dict(code, *args): 46 | """ 47 | 楽天RSSから辞書形式で情報を取り出す(複数の詳細情報問い合わせ可) 48 | 49 | Parameters 50 | ---------- 51 | code : str 52 | args : *str 53 | 54 | Returns 55 | ------- 56 | dict 57 | 58 | Examples 59 | ---------- 60 | >>>rss_dict('9502.T', '始値','銘柄名称','現在値') 61 | {'始値': '1739.50', '現在値': '1661.50', '銘柄名称': '中部電力'} 62 | 63 | 64 | """ 65 | 66 | dde = DDEClient("rss", str(code)) 67 | res = {} 68 | try: 69 | for item in args: 70 | res[item] = dde.request(item).decode('sjis').strip() 71 | except: 72 | print('fail: code@', code) 73 | res = {} 74 | finally: 75 | dde.__del__() 76 | return res 77 | 78 | 79 | def fetch_open(code): 80 | """ 始値を返す(SQ計算用に関数切り出し,入力int) 81 | 82 | Parameters 83 | ---------- 84 | code : int 85 | Examples 86 | --------- 87 | >>> fetch_open(9551) 88 | 50050 89 | """ 90 | 91 | return float(rss(str(code) + '.T', '始値')) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /docs/calc_sq_2019feb01_weekly.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": true 7 | }, 8 | "source": [ 9 | "## SQ計算" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 3, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "# 初期設定:CSVファイルから、日経225社のコード取得しておく\n", 19 | "\n", 20 | "import pandas as pd\n", 21 | "df = pd.read_csv('https://raw.githubusercontent.com/zaq9/nk225_list/master/nk225_list.csv')\n", 22 | "\n" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 8, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | " code name gakumen open\n0 1332 日本水産 50.0 670.0\n1 1333 マルハニチロ 500.0 3730.0\n2 1605 国際石油開発帝石 125.0 1039.0\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "print(df.head(3))" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 7, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "#楽天RSSを利用し、225社の始値を取得 (約20秒)\n", 49 | "\n", 50 | "from rakuten_rss import fetch_open\n", 51 | "\n", 52 | "df['open'] = df.code.apply(lambda x: fetch_open(x))" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 10, 58 | "metadata": {}, 59 | "outputs": [ 60 | { 61 | "name": "stdout", 62 | "output_type": "stream", 63 | "text": [ 64 | "日経平均(SQ値) = 20793.00 \n" 65 | ] 66 | } 67 | ], 68 | "source": [ 69 | "#SQ値計算:(取引所発表 20793.00 と一致)\n", 70 | "\n", 71 | "josuu = 27.003 # 除数 \n", 72 | "df['minashi'] = df['open'] * (50.0 / df.gakumen)\n", 73 | "\n", 74 | "print(f\"日経平均(SQ値) = {df.minashi.sum() / josuu :0.2f} \")" 75 | ] 76 | } 77 | ], 78 | "metadata": { 79 | "kernelspec": { 80 | "display_name": "Python 2", 81 | "language": "python", 82 | "name": "python2" 83 | }, 84 | "language_info": { 85 | "codemirror_mode": { 86 | "name": "ipython", 87 | "version": 2 88 | }, 89 | "file_extension": ".py", 90 | "mimetype": "text/x-python", 91 | "name": "python", 92 | "nbconvert_exporter": "python", 93 | "pygments_lexer": "ipython2", 94 | "version": "2.7.6" 95 | } 96 | }, 97 | "nbformat": 4, 98 | "nbformat_minor": 0 99 | } 100 | -------------------------------------------------------------------------------- /docs/test_rakten_rss.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from rakuten_rss import rss,rss_dict,fetch_open" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 5, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "data": { 21 | "text/plain": [ 22 | "'1739.50'" 23 | ] 24 | }, 25 | "execution_count": 5, 26 | "metadata": {}, 27 | "output_type": "execute_result" 28 | } 29 | ], 30 | "source": [ 31 | "rss('9502.T', '始値')\n" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 4, 37 | "metadata": {}, 38 | "outputs": [ 39 | { 40 | "data": { 41 | "text/plain": [ 42 | "'668.00'" 43 | ] 44 | }, 45 | "execution_count": 4, 46 | "metadata": {}, 47 | "output_type": "execute_result" 48 | } 49 | ], 50 | "source": [ 51 | "rss('9501.T', '始値')\n", 52 | "\n", 53 | "\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "text/plain": [ 64 | "50050.0" 65 | ] 66 | }, 67 | "execution_count": 3, 68 | "metadata": {}, 69 | "output_type": "execute_result" 70 | } 71 | ], 72 | "source": [ 73 | "fetch_open(9983)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 2, 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "data": { 83 | "text/plain": [ 84 | "{'始値': '1739.50', '現在値': '1661.50', '銘柄名称': '中部電力'}" 85 | ] 86 | }, 87 | "execution_count": 2, 88 | "metadata": {}, 89 | "output_type": "execute_result" 90 | } 91 | ], 92 | "source": [ 93 | "rss_dict('9502.T', '始値','銘柄名称','現在値')" 94 | ] 95 | } 96 | ], 97 | "metadata": { 98 | "kernelspec": { 99 | "display_name": "Python 2", 100 | "language": "python", 101 | "name": "python2" 102 | }, 103 | "language_info": { 104 | "codemirror_mode": { 105 | "name": "ipython", 106 | "version": 2 107 | }, 108 | "file_extension": ".py", 109 | "mimetype": "text/x-python", 110 | "name": "python", 111 | "nbconvert_exporter": "python", 112 | "pygments_lexer": "ipython2", 113 | "version": "2.7.6" 114 | } 115 | }, 116 | "nbformat": 4, 117 | "nbformat_minor": 0 118 | } 119 | -------------------------------------------------------------------------------- /lib/ddeclient.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ------------------------------------------------------------------------------- 3 | # Name: ddeclient.py 4 | # Purpose: DDE Management Library (DDEML) client application for communicating 5 | # with Zemax 6 | # 7 | # Notes: This code has been adapted from David Naylor's dde-client code from 8 | # ActiveState's Python recipes (Revision 1). 9 | # Author of original Code: David Naylor, Apr 2011 10 | # Modified by Indranil Sinharoy 11 | # Copyright: (c) David Naylor 12 | # Licence: New BSD license (Please see the file Notice.txt for further details) 13 | # Website: http://code.activestate.com/recipes/577654-dde-client/ 14 | # ------------------------------------------------------------------------------- 15 | import sys 16 | from ctypes import c_int, c_double, c_char_p, c_void_p, c_ulong, c_char, pointer, cast 17 | from ctypes import windll, byref, create_string_buffer, Structure, sizeof 18 | from ctypes import POINTER, WINFUNCTYPE 19 | from ctypes.wintypes import BOOL, HWND, MSG, DWORD, BYTE, INT, LPCWSTR, UINT, ULONG, LPCSTR 20 | 21 | # DECLARE_HANDLE(name) typedef void *name; 22 | HCONV = c_void_p # = DECLARE_HANDLE(HCONV) 23 | HDDEDATA = c_void_p # = DECLARE_HANDLE(HDDEDATA) 24 | HSZ = c_void_p # = DECLARE_HANDLE(HSZ) 25 | LPBYTE = c_char_p # POINTER(BYTE) 26 | LPDWORD = POINTER(DWORD) 27 | LPSTR = c_char_p 28 | ULONG_PTR = c_ulong 29 | 30 | # See windows/ddeml.h for declaration of struct CONVCONTEXT 31 | PCONVCONTEXT = c_void_p 32 | 33 | # DDEML errors 34 | DMLERR_NO_ERROR = 0x0000 # No error 35 | DMLERR_ADVACKTIMEOUT = 0x4000 # request for synchronous advise transaction timed out 36 | DMLERR_DATAACKTIMEOUT = 0x4002 # request for synchronous data transaction timed out 37 | DMLERR_DLL_NOT_INITIALIZED = 0x4003 # DDEML functions called without iniatializing 38 | DMLERR_EXECACKTIMEOUT = 0x4006 # request for synchronous execute transaction timed out 39 | DMLERR_NO_CONV_ESTABLISHED = 0x400a # client's attempt to establish a conversation has failed (can happen during DdeConnect) 40 | DMLERR_POKEACKTIMEOUT = 0x400b # A request for a synchronous poke transaction has timed out. 41 | DMLERR_POSTMSG_FAILED = 0x400c # An internal call to the PostMessage function has failed. 42 | DMLERR_SERVER_DIED = 0x400e 43 | 44 | # Predefined Clipboard Formats 45 | CF_TEXT = 1 46 | CF_BITMAP = 2 47 | CF_METAFILEPICT = 3 48 | CF_SYLK = 4 49 | CF_DIF = 5 50 | CF_TIFF = 6 51 | CF_OEMTEXT = 7 52 | CF_DIB = 8 53 | CF_PALETTE = 9 54 | CF_PENDATA = 10 55 | CF_RIFF = 11 56 | CF_WAVE = 12 57 | CF_UNICODETEXT = 13 58 | CF_ENHMETAFILE = 14 59 | CF_HDROP = 15 60 | CF_LOCALE = 16 61 | CF_DIBV5 = 17 62 | CF_MAX = 18 63 | 64 | # DDE constants for wStatus field 65 | DDE_FACK = 0x8000 66 | DDE_FBUSY = 0x4000 67 | DDE_FDEFERUPD = 0x4000 68 | DDE_FACKREQ = 0x8000 69 | DDE_FRELEASE = 0x2000 70 | DDE_FREQUESTED = 0x1000 71 | DDE_FAPPSTATUS = 0x00FF 72 | DDE_FNOTPROCESSED = 0x0000 73 | 74 | DDE_FACKRESERVED = (~(DDE_FACK | DDE_FBUSY | DDE_FAPPSTATUS)) 75 | DDE_FADVRESERVED = (~(DDE_FACKREQ | DDE_FDEFERUPD)) 76 | DDE_FDATRESERVED = (~(DDE_FACKREQ | DDE_FRELEASE | DDE_FREQUESTED)) 77 | DDE_FPOKRESERVED = (~(DDE_FRELEASE)) 78 | 79 | # DDEML Transaction class flags 80 | XTYPF_NOBLOCK = 0x0002 81 | XTYPF_NODATA = 0x0004 82 | XTYPF_ACKREQ = 0x0008 83 | 84 | XCLASS_MASK = 0xFC00 85 | XCLASS_BOOL = 0x1000 86 | XCLASS_DATA = 0x2000 87 | XCLASS_FLAGS = 0x4000 88 | XCLASS_NOTIFICATION = 0x8000 89 | 90 | XTYP_ERROR = (0x0000 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 91 | XTYP_ADVDATA = (0x0010 | XCLASS_FLAGS) 92 | XTYP_ADVREQ = (0x0020 | XCLASS_DATA | XTYPF_NOBLOCK) 93 | XTYP_ADVSTART = (0x0030 | XCLASS_BOOL) 94 | XTYP_ADVSTOP = (0x0040 | XCLASS_NOTIFICATION) 95 | XTYP_EXECUTE = (0x0050 | XCLASS_FLAGS) 96 | XTYP_CONNECT = (0x0060 | XCLASS_BOOL | XTYPF_NOBLOCK) 97 | XTYP_CONNECT_CONFIRM = (0x0070 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 98 | XTYP_XACT_COMPLETE = (0x0080 | XCLASS_NOTIFICATION) 99 | XTYP_POKE = (0x0090 | XCLASS_FLAGS) 100 | XTYP_REGISTER = (0x00A0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 101 | XTYP_REQUEST = (0x00B0 | XCLASS_DATA) 102 | XTYP_DISCONNECT = (0x00C0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 103 | XTYP_UNREGISTER = (0x00D0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 104 | XTYP_WILDCONNECT = (0x00E0 | XCLASS_DATA | XTYPF_NOBLOCK) 105 | XTYP_MONITOR = (0x00F0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK) 106 | 107 | XTYP_MASK = 0x00F0 108 | XTYP_SHIFT = 4 109 | 110 | # DDE Timeout constants 111 | TIMEOUT_ASYNC = 0xFFFFFFFF 112 | 113 | # DDE Application command flags / Initialization flag (afCmd) 114 | APPCMD_CLIENTONLY = 0x00000010 115 | 116 | # Code page for rendering string. 117 | CP_WINANSI = 1004 # default codepage for windows & old DDE convs. 118 | CP_WINUNICODE = 1200 119 | 120 | # Declaration 121 | DDECALLBACK = WINFUNCTYPE(HDDEDATA, UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, ULONG_PTR, ULONG_PTR) 122 | 123 | # PyZDDE specific globals 124 | number_of_apps_communicating = 0 # to keep an account of the number of zemax 125 | 126 | 127 | # server objects --'ZEMAX', 'ZEMAX1' etc 128 | 129 | class CreateServer(object): 130 | """This is really just an interface class so that PyZDDE can use either the 131 | current dde code or the pywin32 transparently. This object is created only 132 | once. The class name cannot be anything else if compatibility has to be maintained 133 | between pywin32 and this dde code. 134 | """ 135 | 136 | def __init__(self): 137 | self.serverName = 'None' 138 | 139 | def Create(self, client): 140 | """Set a DDE client that will communicate with the DDE server 141 | 142 | Parameters 143 | ---------- 144 | client : string 145 | Name of the DDE client, most likely this will be 'ZCLIENT' 146 | """ 147 | self.clientName = client # shall be used in `CreateConversation` 148 | 149 | def Shutdown(self, createConvObj): 150 | """The shutdown should ideally be requested only once per CreateConversation 151 | object by the PyZDDE module, but for ALL CreateConversation objects, if there 152 | are more than one. If multiple CreateConversation objects were created and 153 | then not cleared, there will be memory leak, and eventually the program will 154 | error out when run multiple times 155 | 156 | Parameters 157 | ---------- 158 | createConvObj : CreateConversation object 159 | 160 | Exceptions 161 | ---------- 162 | An exception occurs if a Shutdown is attempted with a CreateConvObj that 163 | doesn't have a conversation object (connection with ZEMAX established) 164 | """ 165 | global number_of_apps_communicating 166 | # print("Shutdown requested by {}".format(repr(createConvObj))) # for debugging 167 | if number_of_apps_communicating > 0: 168 | # print("Deleting object ...") # for debugging 169 | createConvObj.ddec.__del__() 170 | number_of_apps_communicating -= 1 171 | 172 | 173 | class CreateConversation(object): 174 | """This is really just an interface class so that PyZDDE can use either the 175 | current dde code or the pywin32 transparently. 176 | 177 | Multiple objects of this type may be instantiated depending upon the 178 | number of simultaneous channels of communication with Zemax that the user 179 | program wants to establish using `ln = pyz.PyZDDE()` followed by `ln.zDDEInit()` 180 | calls. 181 | """ 182 | 183 | def __init__(self, ddeServer): 184 | """ 185 | Parameters 186 | ---------- 187 | ddeServer : 188 | d 189 | """ 190 | self.ddeClientName = ddeServer.clientName 191 | self.ddeServerName = 'None' 192 | self.ddetimeout = 50 # default dde timeout = 50 seconds 193 | 194 | def ConnectTo(self, appName, data=None): 195 | """Exceptional error is handled in zdde Init() method, so the exception 196 | must be re-raised""" 197 | global number_of_apps_communicating 198 | self.ddeServerName = appName 199 | try: 200 | self.ddec = DDEClient(self.ddeServerName, self.ddeClientName) # establish conversation 201 | except DDEError: 202 | raise 203 | else: 204 | number_of_apps_communicating += 1 205 | # print("Number of apps communicating: ", number_of_apps_communicating) # for debugging 206 | 207 | def Request(self, item, timeout=None): 208 | """Request DDE client 209 | timeout in seconds 210 | Note ... handle the exception within this function. 211 | """ 212 | if not timeout: 213 | timeout = self.ddetimeout 214 | try: 215 | reply = self.ddec.request(item, int(timeout * 1000)) # convert timeout into milliseconds 216 | except DDEError: 217 | err_str = str(sys.exc_info()[1]) 218 | error = err_str[err_str.find('err=') + 4:err_str.find('err=') + 10] 219 | if error == hex(DMLERR_DATAACKTIMEOUT): 220 | print("TIMEOUT REACHED. Please use a higher timeout.\n") 221 | if (sys.version_info > (3, 0)): # this is only evaluated in case of an error 222 | reply = b'-998' # Timeout error value 223 | else: 224 | reply = '-998' # Timeout error value 225 | return reply 226 | 227 | def RequestArrayTrace(self, ddeRayData, timeout=None): 228 | """Request bulk ray tracing 229 | 230 | Parameters 231 | ---------- 232 | ddeRayData : the ray data for array trace 233 | """ 234 | pass 235 | # TO DO!!! 236 | # 1. Assign proper timeout as in Request() function 237 | # 2. Create the rayData structure conforming to ctypes structure 238 | # 3. Process the reply and return ray trace data 239 | # 4. Handle errors 240 | # reply = self.ddec.poke("RayArrayData", rayData, timeout) 241 | 242 | def SetDDETimeout(self, timeout): 243 | """Set DDE timeout 244 | timeout : timeout in seconds 245 | """ 246 | self.ddetimeout = timeout 247 | 248 | def GetDDETimeout(self): 249 | """Returns the current timeout value in seconds 250 | """ 251 | return self.ddetimeout 252 | 253 | 254 | def get_winfunc(libname, funcname, restype=None, argtypes=(), _libcache={}): 255 | """Retrieve a function from a library/DLL, and set the data types.""" 256 | if libname not in _libcache: 257 | _libcache[libname] = windll.LoadLibrary(libname) 258 | func = getattr(_libcache[libname], funcname) 259 | func.argtypes = argtypes 260 | func.restype = restype 261 | return func 262 | 263 | 264 | class DDE(object): 265 | """Object containing all the DDEML functions""" 266 | AccessData = get_winfunc("user32", "DdeAccessData", LPBYTE, (HDDEDATA, LPDWORD)) 267 | ClientTransaction = get_winfunc("user32", "DdeClientTransaction", HDDEDATA, 268 | (LPBYTE, DWORD, HCONV, HSZ, UINT, UINT, DWORD, LPDWORD)) 269 | Connect = get_winfunc("user32", "DdeConnect", HCONV, (DWORD, HSZ, HSZ, PCONVCONTEXT)) 270 | CreateDataHandle = get_winfunc("user32", "DdeCreateDataHandle", HDDEDATA, 271 | (DWORD, LPBYTE, DWORD, DWORD, HSZ, UINT, UINT)) 272 | CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleW", HSZ, (DWORD, LPCWSTR, UINT)) # Unicode version 273 | # CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleA", HSZ, (DWORD, LPCSTR, UINT)) # ANSI version 274 | Disconnect = get_winfunc("user32", "DdeDisconnect", BOOL, (HCONV,)) 275 | GetLastError = get_winfunc("user32", "DdeGetLastError", UINT, (DWORD,)) 276 | Initialize = get_winfunc("user32", "DdeInitializeW", UINT, 277 | (LPDWORD, DDECALLBACK, DWORD, DWORD)) # Unicode version of DDE initialize 278 | # Initialize = get_winfunc("user32", "DdeInitializeA", UINT, (LPDWORD, DDECALLBACK, DWORD, DWORD)) # ANSI version of DDE initialize 279 | FreeDataHandle = get_winfunc("user32", "DdeFreeDataHandle", BOOL, (HDDEDATA,)) 280 | FreeStringHandle = get_winfunc("user32", "DdeFreeStringHandle", BOOL, (DWORD, HSZ)) 281 | QueryString = get_winfunc("user32", "DdeQueryStringA", DWORD, 282 | (DWORD, HSZ, LPSTR, DWORD, c_int)) # ANSI version of QueryString 283 | UnaccessData = get_winfunc("user32", "DdeUnaccessData", BOOL, (HDDEDATA,)) 284 | Uninitialize = get_winfunc("user32", "DdeUninitialize", BOOL, (DWORD,)) 285 | 286 | 287 | class DDEError(RuntimeError): 288 | """Exception raise when a DDE error occures.""" 289 | 290 | def __init__(self, msg, idInst=None): 291 | if idInst is None: 292 | RuntimeError.__init__(self, msg) 293 | else: 294 | RuntimeError.__init__(self, "%s (err=%s)" % (msg, hex(DDE.GetLastError(idInst)))) 295 | 296 | 297 | class DDEClient(object): 298 | """The DDEClient class. 299 | 300 | Use this class to create and manage a connection to a service/topic. To get 301 | classbacks subclass DDEClient and overwrite callback.""" 302 | 303 | def __init__(self, service, topic): 304 | """Create a connection to a service/topic.""" 305 | self._idInst = DWORD(0) # application instance identifier. 306 | self._hConv = HCONV() 307 | 308 | self._callback = DDECALLBACK(self._callback) 309 | # Initialize and register application with DDEML 310 | res = DDE.Initialize(byref(self._idInst), self._callback, APPCMD_CLIENTONLY, 0) 311 | if res != DMLERR_NO_ERROR: 312 | raise DDEError("Unable to register with DDEML (err=%s)" % hex(res)) 313 | 314 | hszServName = DDE.CreateStringHandle(self._idInst, service, CP_WINUNICODE) 315 | hszTopic = DDE.CreateStringHandle(self._idInst, topic, CP_WINUNICODE) 316 | # Try to establish conversation with the Zemax server 317 | self._hConv = DDE.Connect(self._idInst, hszServName, hszTopic, PCONVCONTEXT()) 318 | DDE.FreeStringHandle(self._idInst, hszTopic) 319 | DDE.FreeStringHandle(self._idInst, hszServName) 320 | if not self._hConv: 321 | raise DDEError("Unable to establish a conversation with server", self._idInst) 322 | 323 | def conect(self, service, topic): 324 | hszServName = DDE.CreateStringHandle(self._idInst, service, CP_WINUNICODE) 325 | hszTopic = DDE.CreateStringHandle(self._idInst, topic, CP_WINUNICODE) 326 | # Try to establish conversation with the Zemax server 327 | self._hConv = DDE.Connect(self._idInst, hszServName, hszTopic, PCONVCONTEXT()) 328 | DDE.FreeStringHandle(self._idInst, hszTopic) 329 | DDE.FreeStringHandle(self._idInst, hszServName) 330 | if not self._hConv: 331 | raise DDEError("Unable to establish a conversation with server", self._idInst) 332 | 333 | def __del__(self): 334 | """Cleanup any active connections and free all DDEML resources.""" 335 | if self._hConv: 336 | DDE.Disconnect(self._hConv) 337 | if self._idInst: 338 | DDE.Uninitialize(self._idInst) 339 | 340 | def advise(self, item, stop=False): 341 | """Request updates when DDE data changes.""" 342 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 343 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, 344 | XTYP_ADVSTOP if stop else XTYP_ADVSTART, TIMEOUT_ASYNC, LPDWORD()) 345 | DDE.FreeStringHandle(self._idInst, hszItem) 346 | if not hDdeData: 347 | raise DDEError("Unable to %s advise" % ("stop" if stop else "start"), self._idInst) 348 | DDE.FreeDataHandle(hDdeData) 349 | 350 | def execute(self, command): 351 | """Execute a DDE command.""" 352 | pData = c_char_p(command) 353 | cbData = DWORD(len(command) + 1) 354 | hDdeData = DDE.ClientTransaction(pData, cbData, self._hConv, HSZ(), CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, 355 | LPDWORD()) 356 | if not hDdeData: 357 | raise DDEError("Unable to send command", self._idInst) 358 | DDE.FreeDataHandle(hDdeData) 359 | 360 | def request(self, item, timeout=5000): 361 | """Request data from DDE service.""" 362 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 363 | # hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, LPDWORD()) 364 | pdwResult = DWORD(0) 365 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, 366 | byref(pdwResult)) 367 | DDE.FreeStringHandle(self._idInst, hszItem) 368 | if not hDdeData: 369 | raise DDEError("Unable to request item", self._idInst) 370 | 371 | if timeout != TIMEOUT_ASYNC: 372 | pdwSize = DWORD(0) 373 | pData = DDE.AccessData(hDdeData, byref(pdwSize)) 374 | if not pData: 375 | DDE.FreeDataHandle(hDdeData) 376 | raise DDEError("Unable to access data in request function", self._idInst) 377 | DDE.UnaccessData(hDdeData) 378 | else: 379 | pData = None 380 | DDE.FreeDataHandle(hDdeData) 381 | 382 | 383 | return pData 384 | 385 | def poke(self, item, data, timeout=5000): 386 | """Poke (unsolicited) data to DDE server""" 387 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE) 388 | pData = c_char_p(data) 389 | cbData = DWORD(len(data) + 1) 390 | pdwResult = DWORD(0) 391 | # hData = DDE.CreateDataHandle(self._idInst, data, cbData, 0, hszItem, CP_WINUNICODE, 0) 392 | # hDdeData = DDE.ClientTransaction(hData, -1, self._hConv, hszItem, CF_TEXT, XTYP_POKE, timeout, LPDWORD()) 393 | hDdeData = DDE.ClientTransaction(pData, cbData, self._hConv, hszItem, CF_TEXT, XTYP_POKE, timeout, 394 | byref(pdwResult)) 395 | DDE.FreeStringHandle(self._idInst, hszItem) 396 | # DDE.FreeDataHandle(dData) 397 | if not hDdeData: 398 | print("Value of pdwResult: ", pdwResult) 399 | raise DDEError("Unable to poke to server", self._idInst) 400 | 401 | if timeout != TIMEOUT_ASYNC: 402 | pdwSize = DWORD(0) 403 | pData = DDE.AccessData(hDdeData, byref(pdwSize)) 404 | if not pData: 405 | DDE.FreeDataHandle(hDdeData) 406 | raise DDEError("Unable to access data in poke function", self._idInst) 407 | # TODO: use pdwSize 408 | DDE.UnaccessData(hDdeData) 409 | else: 410 | pData = None 411 | DDE.FreeDataHandle(hDdeData) 412 | return pData 413 | 414 | def callback(self, value, item=None): 415 | """Callback function for advice.""" 416 | print("callback: %s: %s" % (item, value)) 417 | 418 | def _callback(self, wType, uFmt, hConv, hsz1, hsz2, hDdeData, dwData1, dwData2): 419 | """DdeCallback callback function for processing Dynamic Data Exchange (DDE) 420 | transactions sent by DDEML in response to DDE events 421 | 422 | Parameters 423 | ---------- 424 | wType : transaction type (UINT) 425 | uFmt : clipboard data format (UINT) 426 | hConv : handle to conversation (HCONV) 427 | hsz1 : handle to string (HSZ) 428 | hsz2 : handle to string (HSZ) 429 | hDDedata : handle to global memory object (HDDEDATA) 430 | dwData1 : transaction-specific data (DWORD) 431 | dwData2 : transaction-specific data (DWORD) 432 | 433 | Returns 434 | ------- 435 | ret : specific to the type of transaction (HDDEDATA) 436 | """ 437 | if wType == XTYP_ADVDATA: # value of the data item has changed [hsz1 = topic; hsz2 = item; hDdeData = data] 438 | dwSize = DWORD(0) 439 | pData = DDE.AccessData(hDdeData, byref(dwSize)) 440 | if pData: 441 | item = create_string_buffer('\000' * 128) 442 | DDE.QueryString(self._idInst, hsz2, item, 128, CP_WINANSI) 443 | self.callback(pData, item.value) 444 | DDE.UnaccessData(hDdeData) 445 | return DDE_FACK 446 | else: 447 | print("Error: AccessData returned NULL! (err = %s)" % (hex(DDE.GetLastError(self._idInst)))) 448 | if wType == XTYP_DISCONNECT: 449 | print("Disconnect notification received from server") 450 | 451 | return 0 452 | 453 | 454 | def WinMSGLoop(): 455 | """Run the main windows message loop.""" 456 | LPMSG = POINTER(MSG) 457 | LRESULT = c_ulong 458 | GetMessage = get_winfunc("user32", "GetMessageW", BOOL, (LPMSG, HWND, UINT, UINT)) 459 | TranslateMessage = get_winfunc("user32", "TranslateMessage", BOOL, (LPMSG,)) 460 | # restype = LRESULT 461 | DispatchMessage = get_winfunc("user32", "DispatchMessageW", LRESULT, (LPMSG,)) 462 | 463 | msg = MSG() 464 | lpmsg = byref(msg) 465 | while GetMessage(lpmsg, HWND(), 0, 0) > 0: 466 | TranslateMessage(lpmsg) 467 | DispatchMessage(lpmsg) 468 | 469 | 470 | if __name__ == "__main__": 471 | pass 472 | --------------------------------------------------------------------------------