├── .gitignore ├── LICENSE ├── README.md └── read_dlt645.py /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Aixi Wang 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of python_dlt645 nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python_dlt645 2 | A python lib to read DLT645 meter. 3 | -------------------------------------------------------------------------------- /read_dlt645.py: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------------------- 2 | # read_dlt645 v01 -- a tool to test dlt645 meter through IR reader 3 | # BSD license is applied to this code 4 | # 5 | # Copyright by Aixi Wang (aixi.wang@hotmail.com) 6 | # 7 | #--------------------------------------------------------------------------------------------- 8 | import serial 9 | import sys,time 10 | 11 | SERIAL_TIMEOUT_CNT = 10 12 | #------------------------- 13 | # decode_dlt645 14 | #------------------------- 15 | def decode_dlt645(data): 16 | print 'decode_dlt645 hex_str:',data.encode('hex') 17 | if ord(data[0]) != 0x68: 18 | print 'decode_dlt645 fail 1' 19 | return -1,'','' 20 | if ord(data[7]) != 0x68: 21 | print 'decode_dlt645 fail 2' 22 | return -1,'','','' 23 | 24 | # check len 25 | len_1 = len(data) 26 | if len_1 < 12: 27 | print 'decode_dlt645 fail 3' 28 | return -1,'','','' 29 | 30 | len_2 = ord(data[9]) + 12 31 | if len_1 != len_2: 32 | print 'decode_dlt645 fail 4' 33 | return -1,'','','' 34 | 35 | # check tail 0x16 36 | if ord(data[len_2-1]) != 0x16: 37 | print 'decode_dlt645 fail 5' 38 | return -1,'','','' 39 | 40 | # check checksum 41 | cs = 0 42 | for i in xrange(0,len_2-2): 43 | #print hex(ord(data[i])) 44 | cs += ord(data[i]) 45 | 46 | #print 'cs 1:',hex(cs) 47 | cs = cs % 256 48 | #print 'cs 2:',hex(cs) 49 | 50 | if cs != ord(data[len_2-2]): 51 | print 'decode_dlt645 fail 6' 52 | return -1,'','','' 53 | 54 | # extract data (sub 0x33) 55 | if ord(data[9]) > 0: 56 | d_out = '' 57 | for i in xrange(10,10+ord(data[9])): 58 | d_out += chr(ord(data[i])-0x33) 59 | else: 60 | d_out = '' 61 | 62 | return 0, data[1:7],d_out,ord(data[8]) 63 | 64 | #------------------------- 65 | # encode_dlt645 66 | #------------------------- 67 | def encode_dlt645(addr,ctl,lens,data_tag): 68 | 69 | data_tag_2 = '' 70 | lens_data = len(data_tag) 71 | for i in xrange(0,lens_data): 72 | data_tag_2 += chr(ord(data_tag[i])+0x33) 73 | 74 | 75 | s1 = '\x68' + addr + '\x68' + chr(ctl) + chr(lens) + data_tag_2 76 | 77 | 78 | 79 | # caculate cs 80 | cs = 0 81 | len_1 = len(s1) 82 | #print len_1 83 | for i in xrange(0,len_1): 84 | cs += ord(s1[i]) 85 | cs = cs % 256 86 | s1 = s1 + chr(cs) 87 | # add tail 88 | s1 = s1 + '\x16' 89 | 90 | 91 | 92 | print 'encode_dlt645 hex_str:',s1.encode('hex') 93 | return s1 94 | 95 | #------------------------- 96 | # dlt645_get_addr 97 | #------------------------- 98 | def dlt645_get_addr(serial): 99 | #print 'dlt645_get_addr ...' 100 | try: 101 | #cmd2 = '\xfe\xfe\xfe\xfe\x68\xaa\xaa\xaa\xaa\xaa\xaa\x68\x13\x00\xdf\x16' 102 | cmd2 = '\xfe\xfe\xfe\xfe' + encode_dlt645('\xaa\xaa\xaa\xaa\xaa\xaa',0x13,0,'') 103 | 104 | serial.write(cmd2) 105 | time.sleep(0.5) 106 | resp = '' 107 | c = '' 108 | i = 0 109 | while c != '\x16' and i < SERIAL_TIMEOUT_CNT: 110 | c = serial.read(1) 111 | if len(c) > 0: 112 | resp += c 113 | else: 114 | print '.' 115 | i += 1 116 | 117 | if i >= SERIAL_TIMEOUT_CNT: 118 | return -1,0 119 | 120 | resp1 = dlt_645_rm_fe(resp) 121 | 122 | #print 'resp1:',resp1 123 | 124 | ret,addr,data,ctl = decode_dlt645(resp1) 125 | if ret == 0: 126 | return ret,addr 127 | 128 | 129 | except: 130 | print 'dlt645_get_addr exception!' 131 | return -1,'' 132 | 133 | #------------------------- 134 | # dlt_645_rm_fe 135 | #------------------------- 136 | def dlt_645_rm_fe(s): 137 | n = s.find('\x68') 138 | if n > 0: 139 | return s[n:] 140 | else: 141 | return '' 142 | 143 | #------------------------- 144 | # dlt645_read_data 145 | #------------------------- 146 | def dlt645_read_data(serial,addr,data_tag): 147 | #print 'dlt645_read_data ...' 148 | try: 149 | cmd2 = '\xfe\xfe\xfe\xfe' + encode_dlt645(addr,0x11,4,data_tag) 150 | serial.write(cmd2) 151 | time.sleep(0.5) 152 | resp = '' 153 | c = '' 154 | i = 0 155 | while c != '\x16' and i < SERIAL_TIMEOUT_CNT: 156 | c = serial.read(1) 157 | if len(c) > 0: 158 | resp += c 159 | else: 160 | print '.' 161 | i += 1 162 | 163 | if i >= SERIAL_TIMEOUT_CNT: 164 | return -1,0 165 | 166 | resp1 = dlt_645_rm_fe(resp) 167 | ret,addr,data,ctl = decode_dlt645(resp1) 168 | #print data.encode('hex') 169 | if ret == 0 and len(data) >= 8: 170 | i = ord(data[7])/16 *10000000 171 | i += ord(data[7])%16 *1000000 172 | 173 | i += ord(data[6])/16 *100000 174 | i += ord(data[6])%16 *10000 175 | 176 | i += ord(data[5])/16 *1000 177 | i += ord(data[5])%16 *100 178 | 179 | i += ord(data[4])/16 *10 180 | i += ord(data[4])%16 181 | 182 | return ret,i 183 | else: 184 | return -1,0 185 | except: 186 | print 'dlt645_read_data exception!' 187 | return -1,0 188 | 189 | #------------------------- 190 | # read_dlt645_once 191 | #------------------------- 192 | def read_dlt645_once(serial_port,baud_rate): 193 | try: 194 | serialport_baud = baud_rate 195 | serialport_path = serial_port 196 | s = serial.Serial(serialport_path,serialport_baud,parity=serial.PARITY_EVEN,timeout=0.1) 197 | #print s 198 | 199 | except: 200 | print 'init serial error!' 201 | return -1,0,0,0 202 | 203 | # get meter data, unit : 0.01 kWh 204 | 205 | retcode,addr = dlt645_get_addr(s) 206 | print retcode,addr.encode('hex') 207 | if retcode < 0: 208 | print 'get addr error' 209 | return -1,0,0,0 210 | 211 | print '-----------------------------------' 212 | retcode,data = dlt645_read_data(s,addr,'\x00\x00\x00\x00') 213 | print retcode,data 214 | if retcode == 0: 215 | f1 = data/100.0 216 | print 'total kWh:',f1 217 | else: 218 | return -1,0,0,0 219 | 220 | retcode,data = dlt645_read_data(s,addr,'\x00\x01\x00\x00') 221 | #print retcode,data 222 | if retcode == 0: 223 | f2 = data/100.0 224 | print 'ping kWh:',f2 225 | else: 226 | return -1,0,0,0 227 | retcode,data = dlt645_read_data(s,addr,'\x00\x02\x00\x00') 228 | #print retcode,data 229 | if retcode == 0: 230 | f3 = data/100.0 231 | print 'gu kWh:',f3 232 | else: 233 | return -1,0,0,0 234 | 235 | s.close() 236 | 237 | return 0,f1,f2,f3 238 | 239 | #------------------------- 240 | # main 241 | #------------------------- 242 | if __name__ == '__main__': 243 | ret,f1,f2,f3 = read_dlt645_once('/dev/ttyUSB1',1200) 244 | print ret,f1,f2,f3 245 | 246 | try: 247 | serialport_baud = int(sys.argv[1]) 248 | serialport_path = sys.argv[2] 249 | s = serial.Serial(serialport_path,serialport_baud,parity=serial.PARITY_EVEN,timeout=0.1) 250 | #print s 251 | 252 | # test encode_dlt645 253 | s1 = encode_dlt645('\xaa\xaa\xaa\xaa\xaa\xaa',0x13,0,'') 254 | if s1 == '\x68\xaa\xaa\xaa\xaa\xaa\xaa\x68\x13\x00\xdf\x16': 255 | print 'encode_dlt645 passed' 256 | else: 257 | print 'encode_dlt645 failed' 258 | 259 | # test decode_dlt645 260 | s1 = '\xfe\xfe\xfe\xfe\x68\x69\x40\x17\x10\x12\x00\x68\x93\x06\x9c\x73\x4a\x43\x45\x33\x5f\x16' 261 | s2 = dlt_645_rm_fe(s1) 262 | 263 | retcode,addr,data,ctl = decode_dlt645(s2) 264 | if retcode == 0 and addr == '\x69\x40\x17\x10\x12\x00' and data == '\x69\x40\x17\x10\x12\x00' and ctl == 0x93: 265 | print 'decode_dlt645 passed' 266 | else: 267 | print 'decode_dlt645 failed' 268 | 269 | except: 270 | print 'init serial error!' 271 | sys.exit(-1) 272 | 273 | 274 | # get meter data, unit : 0.01 kWh 275 | while True: 276 | # get meter addr 277 | retcode,addr = dlt645_get_addr(s) 278 | #print retcode,addr 279 | if retcode < 0: 280 | print 'get addr error' 281 | time.sleep(5) 282 | continue 283 | 284 | print '-----------------------------------' 285 | retcode,data = dlt645_read_data(s,addr,'\x00\x00\x00\x00') 286 | #print retcode,data 287 | if retcode == 0: 288 | print 'total kWh:',data/100.0 289 | else: 290 | print 'read error!' 291 | 292 | retcode,data = dlt645_read_data(s,addr,'\x00\x01\x00\x00') 293 | #print retcode,data 294 | if retcode == 0: 295 | print 'ping kWh:',data/100.0 296 | else: 297 | print 'read error!' 298 | retcode,data = dlt645_read_data(s,addr,'\x00\x02\x00\x00') 299 | #print retcode,data 300 | if retcode == 0: 301 | print 'gu kWh:',data/100.0 302 | else: 303 | print 'read error!' 304 | 305 | time.sleep(5) 306 | 307 | 308 | s.close() 309 | --------------------------------------------------------------------------------