├── .gitignore ├── BMS Protocol _CAN_ _Can_20161103.pdf ├── LogPylonTech.py ├── PYLON LFP Battery communication protocol - RS232 V2.8 20161216.pdf ├── PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf ├── PylonTechCHKSM.ods ├── PylontechUS2000BInstall_pinouts.pdf ├── Python_485Connector.PNG ├── README.md ├── RS485-protocol-pylon-low-voltage-V3.3-20180821 (1).pdf ├── RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf ├── ToetsPylonTechSeriePoort.ipynb ├── UnderstandSerialPorts.md ├── asciifull.gif ├── pyloncom.py └── pylonpacket.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 | 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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /BMS Protocol _CAN_ _Can_20161103.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/BMS Protocol _CAN_ _Can_20161103.pdf -------------------------------------------------------------------------------- /LogPylonTech.py: -------------------------------------------------------------------------------- 1 | def twos_complement(hexstr,bits): 2 | # Verander die heksadesimale getal na 'n heelgetal met 'n teken oftewel 'n "signed integer" 3 | # In 'n n-greep twee komplement getallevoorstelling, het die grepe die waardes: 4 | # greep 0 = 2^0 5 | # bit 1 = 2^1 6 | # bit n-2 = -2^(n-2) 7 | # bit n-1 = -2n-1 8 | # 9 | # Maar greep n-1 het waarde 2^(n-1) wanneer dit geen teken het nie, dus is die getal 2^n te hoog. 10 | # Trek dus 2^n af indien greep n-1 'n waarde het 11 | 12 | value = int(hexstr,16) 13 | if value & (1 << (bits-1)): 14 | value -= 1 << bits 15 | 16 | return value 17 | 18 | def wysHeksString(insetstring): 19 | print('Heksadesimale ASCII waarde') 20 | print(insetstring) 21 | print('Heksadesimale string') 22 | getalinheks = bytearray.fromhex(insetstring).decode() 23 | print(getalinheks) 24 | print('Desimale string') 25 | print(int(getalinheks, 16)) 26 | 27 | return 0 28 | 29 | def uitsetGetalHeksString(insetstring): 30 | # Neem die insetstring van die battery ontvang en kry die ASCII waarde daarvan 31 | getalinheks = bytearray.fromhex(insetstring).decode() 32 | # getalinheks word nou omgeskakel na 'n desimale waarde wat gebruik kan word. 33 | uitsetgetal = int(getalinheks, 16) 34 | 35 | return uitsetgetal 36 | 37 | def uitsetGetalHeksStringSignInt(insetstring): 38 | # Neem die insetstring van die battery ontvang en kry die ASCII waarde daarvan 39 | getalinheks = bytearray.fromhex(insetstring).decode() 40 | # getalinheks word nou omgeskakel na 'n desimale waarde wat gebruik kan word. 41 | uitsetgetal = twos_complement(getalinheks, 16) 42 | 43 | return uitsetgetal 44 | 45 | 46 | def skryfLogLynBattery(leesvanbattery): 47 | a = leesvanbattery 48 | 49 | #Stroom in milliAmpere/100. Dus vermenigvuldig met 100 en deel deur 1000 om Ampere te kry 50 | stroom = uitsetGetalHeksStringSignInt(a[-78:-70]) 51 | #Spanning in milliVolt van hele battery 52 | spanning = uitsetGetalHeksString(a[-70:-62]) 53 | #Oorblywende energie in battery 54 | energieOor = uitsetGetalHeksString(a[-62:-54]) 55 | # Totale energie in battery 56 | energietotaal = uitsetGetalHeksString(a[-50:-42]) 57 | # Siklusse 58 | siklusse = uitsetGetalHeksString(a[-42:-34]) 59 | 60 | uitsetloglyn = str(stroom) + ',' + str(spanning) + ',' + str(energieOor) + ',' + str(energietotaal) + ',' + str(siklusse) 61 | 62 | return uitsetloglyn 63 | 64 | 65 | import datetime 66 | import time 67 | from binascii import unhexlify 68 | import serial 69 | 70 | # Log parameters 71 | monsterfrekwensie = 30 # [sekondes] 72 | totalesekondes = 2*60 #24*60*60 # [sekondes] 73 | 74 | # Opdrag vanaf paragraaf 5 in seriepoort handleiding: Lees analoog data 75 | bytestosend = '7E3230303134363432453030323031464433350D' 76 | 77 | begintyd = datetime.datetime.now() 78 | 79 | # Maak leer oop 80 | leer = open('PylonTech_' + str(datetime.date.today()) + '.log', 'w') 81 | leer.write('DatumTyd, Stroom mA_100, Spanning mV, Energie oor mAh, Totale energie mAh, Siklusse\n') 82 | 83 | while (datetime.datetime.now() - begintyd).seconds < totalesekondes: 84 | # Stuur data 1200 baud. Dit moet eers teen hierdie spoed gestuur word 85 | with serial.Serial('/dev/ttyUSB0', 1200, timeout=5.0) as ser: 86 | x = ser.write(unhexlify(bytestosend)) # Stuur opdrag na die battery 87 | uitstring = ser.read(1000) 88 | a = uitstring.hex() 89 | 90 | # Log data na leer 91 | leer.write(str(datetime.datetime.now()) + ',' + skryfLogLynBattery(a)) 92 | leer.write('\n') 93 | time.sleep(monsterfrekwensie - 5) 94 | 95 | leer.close() 96 | -------------------------------------------------------------------------------- /PYLON LFP Battery communication protocol - RS232 V2.8 20161216.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/PYLON LFP Battery communication protocol - RS232 V2.8 20161216.pdf -------------------------------------------------------------------------------- /PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf -------------------------------------------------------------------------------- /PylonTechCHKSM.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/PylonTechCHKSM.ods -------------------------------------------------------------------------------- /PylontechUS2000BInstall_pinouts.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/PylontechUS2000BInstall_pinouts.pdf -------------------------------------------------------------------------------- /Python_485Connector.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/Python_485Connector.PNG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PylonTechBattery 2 | Lees data van die PylonTech battery deur die seriepoort van rekenaar 3 | 4 | 5 | 6 | Gebruik die volgende bronne om die battery intervlak te programmeer: 7 | 8 | Hier is die Duitse forum waar die PDF intervlakke vandaan kom: 9 | 10 | [https://www.photovoltaikforum.com/thread/118958-pylontech-us2000b-daten-%C3%BCber-konsole-rs232-auslesen/?t=118958&start=10](https://www.photovoltaikforum.com/thread/118958-pylontech-us2000b-daten-über-konsole-rs232-auslesen/?t=118958&start=10) 11 | 12 | Hier is 'n gebruiker wat 'n python intervlak geskryf het: 13 | 14 | https://github.com/celsworth/lxp-pylon-utils 15 | 16 | https://www.navitron.org.uk/forum/index.php?topic=30961.0 17 | 18 | 19 | Hier is gebruiker wat javascript intervlak geskryf het. 20 | https://github.com/geeks-r-us/solar-sis 21 | 22 | 23 | 24 | Bronne vanaf Duitse forum 25 | 26 | https://www.photovoltaikforum.com/thread/130061-pylontech-us2000b-daten-protokolle-programme/ 27 | 28 | [https://www.photovoltaikforum.com/thread/118958-pylontech-us2000b-daten-%C3%BCber-konsole-rs232-auslesen/?t=118958&start=10](https://www.photovoltaikforum.com/thread/118958-pylontech-us2000b-daten-über-konsole-rs232-auslesen/?t=118958&start=10) 29 | 30 | 31 | 32 | ## Pylontech RS-232 formaat van seriepoort 33 | 34 | Hier is die ASCII tabel. Die Pylontech stuur data in Heksadesimale getalle, maar dit gebruik die heksadesimale waarde van die ASCII tabel. 35 | 36 | ![asciifull](asciifull.gif) 37 | 38 | Voorbeeld uit paragraaf 5 van RS-232 handleiding van Pylontech battery: 39 | 40 | Stuur volgende string HEX getalle na die battery: 41 | 42 | `7E 32 30 30 31 34 36 34 32 45 30 30 32 30 31 46 44 33 35 0D` 43 | 44 | Dit beteken 45 | `7E` - begin van data 46 | 47 | `32 30` - 20 in ASCII dit is die weergawe (VER) 48 | 49 | `30 31` - 30 is 'n 0 in ASCII in heks en 31 is 'n 1 in heks in ASCII, dus 01 dit is die battery nommer of ADR veld 50 | 51 | `34 36` - 34 is 'n 4 in ASCII in heks en 36 is 'n 6 in heks in ASCII, dus 46 en dit is die kode vir litium ioon batterye (CID1) 52 | 53 | `34 32` - 42 dit is die kode vir analoog inligting vanaf battery (CID2) 54 | 55 | `45 30 30 32` - LENGTH dit is die lengte van die data wat met 'n komplekse berekening bereken word 56 | 57 | `30 31` - INFO Dit is die ASCII vir 01 wat beteken jy vra vir battery 1 se inligting 58 | 59 | `46 44 33 35` - CHECKSUM Dit is die toets som vir hierdie string data 60 | 61 | `0D` - dit is die einde van die data oftewel "Carriage return" 62 | 63 | Kry dan terug: 64 | 65 | `7E 32 30 30 31 34 36 30 30 43 30 36 45 31 31 30 31 30 66 | 46 30 44 34 35 30 44 34 34 30 44 34 35 30 44 34 34 30 44 34 35 30 44 34 67 | 34 30 44 33 45 30 44 34 35 30 44 34 41 30 44 34 41 30 44 34 42 30 44 34 68 | 41 30 44 34 41 30 44 34 41 30 44 34 41 30 35 30 42 43 33 30 42 43 33 30 69 | 42 43 33 30 42 43 44 30 42 43 44 30 30 30 30 43 37 32 35 42 46 36 38 30 70 | 32 43 33 35 30 30 30 30 32 45 35 35 33 0D` 71 | 72 | Die oorblywende energie in die battery word gegee deur: 73 | 74 | `42 46 36 38` 75 | 76 | in die string wat teruggestuur word hierbo. Die volledige analise van die string hierbo word gegee in die Pylontech RS232 handleiding in paragraaf 5. 77 | 78 | Die getalle hierbo is die heksadesimale waardes van die ASCII tabel. Dus is `42`eintlik die string `B.` Net so is die hele string dan: `BF68` 79 | 80 | Hierdie string is 'n heksadesimale getal wat nou teruggelei kan word na 'n desimale getal. `BF68H` waar `H` die heksadesimale getal beteken, is `49000`. Hierdie getal is die waarde in mAh van die battery. Dus om die orige energie in kWh te bereken word die volgende berekening gedoen: 81 | 82 | $Energie = \frac{49000mAh}{1000} \times 48V = 2352W = 2.352kW$ 83 | 84 | Hierdie berekening neem aan dit is 'n 48V battery. 85 | 86 | Dus is die lading persentasie (die "State of Charge" of SOC) gelyk aan: 87 | 88 | $SOC = \frac{2.352W}{2400W} = 0.98 = 98\%$ 89 | 90 | Hierdie berekening neem aan dit is 'n 2.4kWh battery oftewel die Pylontech US2000. 91 | 92 | 93 | 94 | ### Battery instellings vir die Pylontech battery op die Axpert inverter 95 | 96 | Die volgende instellings is gedoen vir die Axpert om die Pylontech battery in staat te stel om meer leeg te loop. Dit stel die battery in staat om meer van die sonkrag te benut. Dit verskil van die standaard Pylontech instellings vir die Axpert. Dus moet dit gesien word as meer onveilig (die battery mag leegloop met hierdie instellings wat ongewens is). 97 | 98 | Axpert Program nommer: 99 | 100 | Program 02 - Stel na N*20A, N = aantal batterye 101 | 102 | Program 05 - USE 103 | 104 | Program 12 - 46V 105 | 106 | Program 13 - 48V 107 | 108 | Program 29 - 46V 109 | 110 | Belangrike nota: Met 'n stelsel waar daar baie panele is kan program 02 eerder na die volgende gestel word: N*20A - 10A. Dit is meer geskik want daar is ondervind dat die DC skakelaar uitklink a.g.v. baie groot stroomwaardes wat die battery nie kan absorbeer op kort kennisgewing nie. Indien die laaistroom laer gestel word soos laasgenoemde, verdwyn die probleem. 111 | 112 | ### Task: Display Detected System’s Serial Support 113 | 114 | Simply run dmesg command 115 | 116 | `$ dmesg | grep tty` -------------------------------------------------------------------------------- /RS485-protocol-pylon-low-voltage-V3.3-20180821 (1).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/RS485-protocol-pylon-low-voltage-V3.3-20180821 (1).pdf -------------------------------------------------------------------------------- /RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf -------------------------------------------------------------------------------- /ToetsPylonTechSeriePoort.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "/dev/ttyUSB0\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "# Toets basiese skryf na die seriepoort\n", 18 | "import serial\n", 19 | "ser = serial.Serial('/dev/ttyUSB0') # open serial port\n", 20 | "print(ser.name) # check which port was really used\n", 21 | "#ser.write(b'hello') # write a string\n", 22 | "ser.close() # close port" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "b''\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "# Probeer 1200 baud en 115200\n", 40 | "with serial.Serial('/dev/ttyUSB0', 1200, timeout=1) as ser:\n", 41 | " x = ser.read() # read one byte\n", 42 | " s = ser.read(10) # read up to ten bytes (timeout)\n", 43 | " #line = ser.readline() # read a '\\n' terminated line\n", 44 | " \n", 45 | "print(s)" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 5, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "# Deel die heks uitstring op in die dele wat die data bevat soos \n", 55 | "# die hoeveelheid energie oor in battery\n", 56 | "\n", 57 | "\n", 58 | "def twos_complement(hexstr,bits):\n", 59 | " # Verander die heksadesimale getal na 'n heelgetal met 'n teken oftewel 'n \"signed integer\"\n", 60 | " # In 'n n-greep twee komplement getallevoorstelling, het die grepe die waardes:\n", 61 | " # greep 0 = 2^0\n", 62 | " # bit 1 = 2^1\n", 63 | " # bit n-2 = -2^(n-2)\n", 64 | " # bit n-1 = -2n-1\n", 65 | " #\n", 66 | " # Maar greep n-1 het waarde 2^(n-1) wanneer dit geen teken het nie, dus is die getal 2^n te hoog.\n", 67 | " # Trek dus 2^n af indien greep n-1 'n waarde het\n", 68 | "\n", 69 | " value = int(hexstr,16)\n", 70 | " if value & (1 << (bits-1)):\n", 71 | " value -= 1 << bits\n", 72 | " \n", 73 | " return value\n", 74 | "\n", 75 | "def wysHeksString(insetstring):\n", 76 | " print('Heksadesimale ASCII waarde')\n", 77 | " print(insetstring)\n", 78 | " print('Heksadesimale string')\n", 79 | " getalinheks = bytearray.fromhex(insetstring).decode()\n", 80 | " print(getalinheks)\n", 81 | " print('Desimale string')\n", 82 | " print(int(getalinheks, 16))\n", 83 | " \n", 84 | " return 0\n", 85 | "\n", 86 | "def uitsetGetalHeksString(insetstring):\n", 87 | " # Neem die insetstring van die battery ontvang en kry die ASCII waarde daarvan\n", 88 | " getalinheks = bytearray.fromhex(insetstring).decode()\n", 89 | " # getalinheks word nou omgeskakel na 'n desimale waarde wat gebruik kan word.\n", 90 | " uitsetgetal = int(getalinheks, 16)\n", 91 | " \n", 92 | " return uitsetgetal\n", 93 | "\n", 94 | "def uitsetGetalHeksStringSignInt(insetstring):\n", 95 | " # Neem die insetstring van die battery ontvang en kry die ASCII waarde daarvan\n", 96 | " getalinheks = bytearray.fromhex(insetstring).decode()\n", 97 | " # getalinheks word nou omgeskakel na 'n desimale waarde wat gebruik kan word.\n", 98 | " uitsetgetal = twos_complement(getalinheks, 16)\n", 99 | " \n", 100 | " return uitsetgetal\n", 101 | "\n", 102 | "\n", 103 | "def skryfLogLynBattery(leesvanbattery):\n", 104 | " a = leesvanbattery\n", 105 | " \n", 106 | " #Stroom in milliAmpere/100. Dus vermenigvuldig met 100 en deel deur 1000 om Ampere te kry\n", 107 | " stroom = uitsetGetalHeksStringSignInt(a[-78:-70])\n", 108 | " #Spanning in milliVolt van hele battery\n", 109 | " spanning = uitsetGetalHeksString(a[-70:-62])\n", 110 | " #Oorblywende energie in battery\n", 111 | " energieOor = uitsetGetalHeksString(a[-62:-54])\n", 112 | " # Totale energie in battery\n", 113 | " energietotaal = uitsetGetalHeksString(a[-50:-42])\n", 114 | " # Siklusse\n", 115 | " siklusse = uitsetGetalHeksString(a[-42:-34])\n", 116 | " \n", 117 | " uitsetloglyn = str(stroom) + ',' + str(spanning) + ',' + str(energieOor) + ',' + str(energietotaal) + ',' + str(siklusse)\n", 118 | " \n", 119 | " return uitsetloglyn" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 36, 125 | "metadata": {}, 126 | "outputs": [ 127 | { 128 | "name": "stdout", 129 | "output_type": "stream", 130 | "text": [ 131 | "Greepstring\n", 132 | "b'~20014600F07A11010F0DE30DE30DD10DE30DE30DE50DE30DE40DE70DE60DDC0DE80DE80DE80DE7050BEB0BCD0BCD0BCD0BCD0000D051FFFF04FFFF0010012110012110E1D4\\r'\n", 133 | "Heksadesimale string\n", 134 | "7e3230303134363030463037413131303130463044453330444533304444313044453330444533304445353044453330444534304445373044453630444443304445383044453830444538304445373035304245423042434430424344304243443042434430303030443035314646464630344646464630303130303132313130303132313130453144340d\n", 135 | "0,53329,65535,65535,16\n" 136 | ] 137 | } 138 | ], 139 | "source": [ 140 | "from binascii import unhexlify\n", 141 | "\n", 142 | "# Opdrag vanaf paragraaf 5 in seriepoort handleiding\n", 143 | "bytestosend = '7E3230303134363432453030323031464433350D'\n", 144 | "\n", 145 | "# Stuur data 1200 baud. Dit moet eers teen hierdie spoed gestuur word\n", 146 | "with serial.Serial('/dev/ttyUSB0', 1200, timeout=5.0) as ser:\n", 147 | " x = ser.write(unhexlify(bytestosend)) # Stuur opdrag na die battery\n", 148 | " \n", 149 | " uitstring = ser.read(1000)\n", 150 | "\n", 151 | "# Druk die greepstring (bytestring)\n", 152 | "print('Greepstring')\n", 153 | "print(uitstring)\n", 154 | "\n", 155 | "# Skakel die greepstring om na heksadesimale string\n", 156 | "print('Heksadesimale string')\n", 157 | "a = uitstring.hex()\n", 158 | "print(a) \n", 159 | "\n", 160 | "print(skryfLogLynBattery(a))" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 69, 166 | "metadata": {}, 167 | "outputs": [ 168 | { 169 | "name": "stdout", 170 | "output_type": "stream", 171 | "text": [ 172 | "Aantal siklusse\n", 173 | "Heksadesimale ASCII waarde\n", 174 | "31304532\n", 175 | "Heksadesimale string\n", 176 | "10E2\n", 177 | "Desimale string\n", 178 | "4322\n", 179 | "Totale energie\n", 180 | "Heksadesimale ASCII waarde\n", 181 | "32313130\n", 182 | "Heksadesimale string\n", 183 | "2110\n", 184 | "Desimale string\n", 185 | "8464\n" 186 | ] 187 | }, 188 | { 189 | "data": { 190 | "text/plain": [ 191 | "0" 192 | ] 193 | }, 194 | "execution_count": 69, 195 | "metadata": {}, 196 | "output_type": "execute_result" 197 | } 198 | ], 199 | "source": [ 200 | "print('Aantal siklusse')\n", 201 | "#31313030313231\n", 202 | "#31304532\n", 203 | "wysHeksString(a[-14:-6])\n", 204 | "print('Totale energie')\n", 205 | "wysHeksString(a[-18:-10])\n", 206 | "\n", 207 | "#getalinheks = bytearray.fromhex(\"42463638\").decode()\n", 208 | "#print(getalinheks)\n", 209 | "#print(int(getalinheks, 16))" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "# Voorbeeld uit handleiding\n", 217 | "\n", 218 | "`7E` - Begin inligting\n", 219 | "`32 30` - Weergawe \n", 220 | "`30 31` - Adres \n", 221 | "`34 36` - Kode vir litium battery\n", 222 | "`30 30` - RTN Return kode 30 30 is 00 is alles het vlot verloop \n", 223 | "`43 30 36 45` - Lengte of LEN is die lengte van die data (met CHKSUM ingesluit) \n", 224 | "`31 31 30 31` - INFOFLAG\n", 225 | "\n", 226 | "Data begin\n", 227 | "`30 46` - Aantal selle\n", 228 | "30 44 34 35 - Sel 1 Volts\n", 229 | "30 44 34 34 \n", 230 | "30 44 34 35 \n", 231 | "30 44 34 34 30 44 34 35 30 44 34\n", 232 | "34 30 44 33 45 30 44 34 35 30 44 34 41 30 44 34 41 30 44 34 42 30 44 34\n", 233 | "41 30 44 34 41 30 44 34 41 30 44 34 41 30 35 30 42 43 33 30 42 43 33 30\n", 234 | "42 43 33 30 42 43 44 30 42 43 44 30 30 30 30 43 37 32 35 42 46 36 38 30\n", 235 | "32 \n", 236 | "`43 33 35 30` - Totale energie (mAh) \n", 237 | "`30 30 30 32` - Aantal siklusse\n", 238 | "`45 35 35 33` - Toetssom of CHKSUM\n", 239 | "`0D` - Einde van inligting\n", 240 | "\n", 241 | "# Voorbeeld uitset\n", 242 | "`7e` - Begin inligting \n", 243 | "`32 30` - Weergawe \n", 244 | "`30 31` - Adres \n", 245 | "`34 36` - Kode vir litium battery \n", 246 | "`30 30` - RTN Return kode 30 30 is 00 is alles het vlot verloop \n", 247 | "`46 30 37 41` - Lengte \n", 248 | "`31 31 30 31` - INFOFLAG \n", 249 | "\n", 250 | "Data begin \n", 251 | "`30 46` - Aantal selle (15) \n", 252 | "`30 44 45 32` - Sel 1 Volts \n", 253 | "`30444532` - Sel 2 Volts \n", 254 | "`30444430` - Sel 3 Volts \n", 255 | "`30444533` - Sel 4 Volts \n", 256 | "`30444533` - Sel 5 Volts \n", 257 | "`30444534` - Sel 6 Volts \n", 258 | "`30444532` - Sel 7 Volts \n", 259 | "`30444531` - Sel 8 Volts \n", 260 | "`30444538` - Sel 9 Volts \n", 261 | "`30444537` - Sel 10 Volts \n", 262 | "`30444444` - Sel 11 Volts \n", 263 | "`30444537` - Sel 12 Volts \n", 264 | "`30444538` - Sel 13 Volts \n", 265 | "`30444537` - Sel 14 Volts \n", 266 | "`30444537` - Sel 15 Volts \n", 267 | "`30 35` - Aantal Temperatuur meetpunte \n", 268 | "`30424542` - Temperatuur 1 \n", 269 | "`30424333` - Temperatuur 2 \n", 270 | "`30424333` - Temperatuur 3 \n", 271 | "`30424333` - Temperatuur 4 \n", 272 | "`30424333` - Temperatuur 5 in Kelvin maal 10, dus is hierdie waarde van 3011 -> 301.1 Kelvin oftewel 28.1 grade Celcius. \n", 273 | "\n", 274 | "Stroom en spanning data: \n", 275 | "`30303030` - Stroom in milliAmpere \n", 276 | "`44303441` - Spanning in milliVolt van hele battery (53322mV of 53.3V) \n", 277 | "`46464646` - Oorblywende energie in battery (65535mAh). Hierdie battery is 100% vol of SOC = 100% as jy hierdie waarde deur die totale energie in battery deel. \n", 278 | "`3034` - \"User-defined\" waarde. In hierdie geval 4 \n", 279 | "`46464646` - Totale energie in battery (65535mAh) \n", 280 | "`30303130` - Aantal siklusse van battery. Die battery het al 16 volle siklusse gedoen. \n", 281 | "\n", 282 | "`303132313130303132313130` - Ekstra info \n", 283 | "`45323130` - Checksum \n", 284 | "`0d` - Einde van inligting " 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 4, 290 | "metadata": {}, 291 | "outputs": [ 292 | { 293 | "name": "stdout", 294 | "output_type": "stream", 295 | "text": [ 296 | "Heksadesimale ASCII waarde\n", 297 | "30303130\n", 298 | "Heksadesimale string\n", 299 | "0010\n", 300 | "Desimale string\n", 301 | "16\n", 302 | "30303030\n", 303 | "44303532\n", 304 | "46464646\n", 305 | "46464646\n", 306 | "30303130\n", 307 | "0,53330,65535,65535,16\n" 308 | ] 309 | } 310 | ], 311 | "source": [ 312 | "wysHeksString('30303130')\n", 313 | "#Stroom in milliAmpere\n", 314 | "print(a[-78:-70])\n", 315 | "#Spanning in milliVolt van hele battery\n", 316 | "print(a[-70:-62])\n", 317 | "#Oorblywende energie in battery\n", 318 | "print(a[-62:-54])\n", 319 | "# Totale energie in battery\n", 320 | "print(a[-50:-42])\n", 321 | "# Siklusse\n", 322 | "print(a[-42:-34])\n", 323 | "\n", 324 | "print(skryfLogLynBattery(a))" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": null, 330 | "metadata": {}, 331 | "outputs": [], 332 | "source": [] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": 3, 337 | "metadata": { 338 | "scrolled": true 339 | }, 340 | "outputs": [ 341 | { 342 | "name": "stdout", 343 | "output_type": "stream", 344 | "text": [ 345 | "46464636\n", 346 | "FFF6\n" 347 | ] 348 | } 349 | ], 350 | "source": [ 351 | "# Voorbeeld string\n", 352 | "a='7e3230303134363030463037413131303130463043454630434631304346313043463130434631304346313043463030434631304345453043454630434630304346303043463130434631304346303035304245313042433330424333304243333042423946464636433231344646464630344646464630303130303131383634303132313130453144440d'\n", 353 | "print(a[-78:-70])\n", 354 | "getalinheks = bytearray.fromhex(a[-78:-70]).decode()\n", 355 | "print(getalinheks)" 356 | ] 357 | }, 358 | { 359 | "cell_type": "code", 360 | "execution_count": 37, 361 | "metadata": { 362 | "scrolled": true 363 | }, 364 | "outputs": [], 365 | "source": [ 366 | "import datetime\n", 367 | "import time\n", 368 | "from binascii import unhexlify\n", 369 | "import serial\n", 370 | "\n", 371 | "# Log parameters\n", 372 | "monsterfrekwensie = 30 # [sekondes]\n", 373 | "totalesekondes = 60*5 #24*60*60 # [sekondes]\n", 374 | "\n", 375 | "# Opdrag vanaf paragraaf 5 in seriepoort handleiding: Lees analoog data\n", 376 | "bytestosend = '7E3230303134363432453030323031464433350D'\n", 377 | "\n", 378 | "begintyd = datetime.datetime.now()\n", 379 | "\n", 380 | "# Maak leer oop\n", 381 | "leer = open('PylonTech_' + str(datetime.date.today()) + '.log', 'w')\n", 382 | "leer.write('DatumTyd, Stroom mA_100, Spanning mV, Energie oor mAh, Totale energie mAh, Siklusse\\n')\n", 383 | "\n", 384 | "while (datetime.datetime.now() - begintyd).seconds < totalesekondes:\n", 385 | " # Stuur data 1200 baud. Dit moet eers teen hierdie spoed gestuur word\n", 386 | " with serial.Serial('/dev/ttyUSB0', 1200, timeout=5.0) as ser:\n", 387 | " x = ser.write(unhexlify(bytestosend)) # Stuur opdrag na die battery\n", 388 | " uitstring = ser.read(1000)\n", 389 | " \n", 390 | " # Log data na leer\n", 391 | " leer.write(str(datetime.datetime.now()) + ',' + skryfLogLynBattery(a))\n", 392 | " leer.write('\\n')\n", 393 | " time.sleep(monsterfrekwensie - 5)\n", 394 | "\n", 395 | "leer.close()\n" 396 | ] 397 | }, 398 | { 399 | "cell_type": "code", 400 | "execution_count": 27, 401 | "metadata": {}, 402 | "outputs": [ 403 | { 404 | "name": "stdout", 405 | "output_type": "stream", 406 | "text": [ 407 | "1\n" 408 | ] 409 | } 410 | ], 411 | "source": [ 412 | "begintyd = datetime.datetime.now()\n", 413 | "time.sleep(1)\n", 414 | "totaletyd = datetime.datetime.now() - begintyd\n", 415 | "print(totaletyd.seconds)" 416 | ] 417 | } 418 | ], 419 | "metadata": { 420 | "hide_input": false, 421 | "kernelspec": { 422 | "display_name": "Python 3", 423 | "language": "python", 424 | "name": "python3" 425 | }, 426 | "language_info": { 427 | "codemirror_mode": { 428 | "name": "ipython", 429 | "version": 3 430 | }, 431 | "file_extension": ".py", 432 | "mimetype": "text/x-python", 433 | "name": "python", 434 | "nbconvert_exporter": "python", 435 | "pygments_lexer": "ipython3", 436 | "version": "3.6.3" 437 | }, 438 | "toc": { 439 | "base_numbering": 1, 440 | "nav_menu": {}, 441 | "number_sections": true, 442 | "sideBar": true, 443 | "skip_h1_title": false, 444 | "title_cell": "Table of Contents", 445 | "title_sidebar": "Contents", 446 | "toc_cell": false, 447 | "toc_position": {}, 448 | "toc_section_display": true, 449 | "toc_window_display": false 450 | }, 451 | "varInspector": { 452 | "cols": { 453 | "lenName": 16, 454 | "lenType": 16, 455 | "lenVar": 40 456 | }, 457 | "kernels_config": { 458 | "python": { 459 | "delete_cmd_postfix": "", 460 | "delete_cmd_prefix": "del ", 461 | "library": "var_list.py", 462 | "varRefreshCmd": "print(var_dic_list())" 463 | }, 464 | "r": { 465 | "delete_cmd_postfix": ") ", 466 | "delete_cmd_prefix": "rm(", 467 | "library": "var_list.r", 468 | "varRefreshCmd": "cat(var_dic_list()) " 469 | } 470 | }, 471 | "types_to_exclude": [ 472 | "module", 473 | "function", 474 | "builtin_function_or_method", 475 | "instance", 476 | "_Feature" 477 | ], 478 | "window_display": false 479 | } 480 | }, 481 | "nbformat": 4, 482 | "nbformat_minor": 2 483 | } 484 | -------------------------------------------------------------------------------- /UnderstandSerialPorts.md: -------------------------------------------------------------------------------- 1 | # Seriepoorte in linux en python 2 | 3 | ## Inleiding 4 | 5 | Die RS-232 is die ouer seriepoort formaat en RS-485 is die meer moderne formaat (kan langer kabels ondersteun en meer toestelle as een op dieselfde kabel). 6 | 7 | 8 | 9 | ## Python serial 10 | 11 | Open 'n poort in python: 12 | 13 | Open port at “9600,8,N,1”, no timeout: 14 | 15 | ```python 16 | >>> import serial 17 | >>> ser = serial.Serial('/dev/ttyUSB0') # open serial port 18 | >>> print(ser.name) # check which port was really used 19 | >>> ser.write(b'hello') # write a string 20 | >>> ser.close() # close port 21 | ``` 22 | https://pythonhosted.org/pyserial/shortintro.html 23 | 24 | ## How To Check and Use Serial Ports Under Linux 25 | 26 | Linux offers various tools. Linux uses ttySx for a serial port device name. For example, COM1 (DOS/Windows name) is ttyS0, COM2 is ttyS1 and so on. 27 | 28 | 29 | 30 | ### Task: Display Detected System’s Serial Support 31 | 32 | Simply run dmesg command 33 | 34 | `$ dmesg | grep tty` 35 | 36 | ```bash 37 | [ 37.531286] serial8250: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A 38 | [ 37.531841] 00:0b: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A 39 | [ 37.532138] 0000:04:00.3: ttyS1 at I/O 0x1020 (irq = 18) is a 16550A 40 | ``` 41 | ### setserial command 42 | 43 | setserial is a program designed to set and/or report the configuration information associated with a serial port. This information includes what I/O port and IRQ a particular serial port is using, and whether or not the break key should be interpreted as the Secure Attention Key, and so on. Just type the following command: 44 | 45 | `$ setserial -g /dev/ttyS[0123]` 46 | 47 | Output: 48 | 49 | ```bash 50 | /dev/ttyS0, UART: 16550A, Port: 0x03f8, IRQ: 4 51 | /dev/ttyS1, UART: 16550A, Port: 0x1020, IRQ: 18 52 | /dev/ttyS2, UART: unknown, Port: 0x03e8, IRQ: 4 53 | /dev/ttyS3, UART: unknown, Port: 0x02e8, IRQ: 3 54 | ``` 55 | setserial with -g option help to find out what physical serial ports your Linux box has. -------------------------------------------------------------------------------- /asciifull.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Interster/PylonTechBattery/68c72d21ffa0456de190da4375604082bfa0c769/asciifull.gif -------------------------------------------------------------------------------- /pyloncom.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pylonpacket 3 | import logging 4 | import serial 5 | import time 6 | 7 | 8 | 9 | class PylonCom: 10 | PORT = "/dev/ttyUSB0" 11 | BAUD = 115200 12 | 13 | def __init__(self): 14 | self.sp = serial.Serial(PylonCom.PORT,PylonCom.BAUD,timeout=0.5) 15 | 16 | def GetReply(self, request, reply_type): 17 | self.sp.write(request.GetAsciiBytes()) 18 | line = bytearray() 19 | while True: 20 | c = self.sp.read(1) 21 | if c: 22 | line.extend(c) 23 | if c[0] == 0x0D: 24 | break 25 | else: 26 | break 27 | logging.debug("Received sentence %s",line) 28 | preply = pylonpacket.PylonPacket.Parse(line, reply_type) 29 | return preply 30 | 31 | def close(self): 32 | self.sp.close() 33 | 34 | def main(argv=None): 35 | if argv is None: 36 | argv = sys.argv[1:] 37 | 38 | pc = PylonCom() 39 | 40 | 41 | #for adr in range(0,255): 42 | # ppIn = pylonpacket.PPGetVersionInfo() 43 | # ppIn.ADR=adr 44 | # ppOut=pc.GetReply(ppIn, pylonpacket.PPVersionInfo) 45 | # if ppOut: print("Get protocol version reply:",ppOut) 46 | #return 47 | 48 | 49 | 50 | #ppIn=pylonpacket.PPGetManufacturerInfo() 51 | #print("Get manufacturer info:",ppIn) 52 | #ppOut=pc.GetReply(ppIn, pylonpacket.PPManufacturerInfo) 53 | #print("Get manufacturer info reply:",ppOut) 54 | 55 | ppIn = pylonpacket.PPGetSystemParameter() 56 | print("Get system parameter:",ppIn) 57 | ppOut = pc.GetReply(ppIn, pylonpacket.PPSystemParameter) 58 | print("Get system parameter reply:",ppOut) 59 | 60 | ppIn = pylonpacket.PPGetSeriesNumber() 61 | ppIn.Command = 0x02 62 | print("Get series number:",ppIn) 63 | ppOut = pc.GetReply(ppIn, pylonpacket.PPSeriesNumber) 64 | print("Get series number reply:",ppOut) #,ppOut.info.hex()) 65 | 66 | while True: 67 | for adr in range(2,3): 68 | print("Conectiong to addr",adr) 69 | ppIn = pylonpacket.PPGetChargeManagementInformation() 70 | ppIn.Command = adr 71 | ppIn.ADR = adr 72 | #print("Get charge info:",ppIn) 73 | ppOut = pc.GetReply(ppIn, pylonpacket.PPChargeManagementInformation) 74 | print("Get charge info reply:",ppOut) 75 | 76 | #return 77 | 78 | ppIn = pylonpacket.PPGetAnalogValue() 79 | ppIn.Command = adr 80 | ppIn.ADR = adr 81 | #print("Get analog:",ppIn) 82 | ppOut = pc.GetReply(ppIn, pylonpacket.PPAnalogValue) 83 | print("Get analog reply:",ppOut) 84 | 85 | print("") 86 | time.sleep(2) 87 | 88 | #ppIn=pylonpacket.PPGetChargeManagementInformation() 89 | #ppIn.Command=0x02 90 | #print("Get charge info:",ppIn) 91 | #ppOut=pc.GetReply(ppIn, pylonpacket.PPChargeManagementInformation) 92 | #print("Get charge info reply:",ppOut) 93 | 94 | 95 | ppIn = pylonpacket.PPGetAlarmInformation() 96 | ppIn.Command = 0x02 97 | print("Get alarm info:",ppIn) 98 | ppOut = pc.GetReply(ppIn, pylonpacket.PPAlarmInformation) 99 | print("Get alarm info reply:",ppOut) #,ppOut.info.hex()) 100 | 101 | 102 | ppIn = pylonpacket.PPTurnOff() 103 | ppIn.Command = 0x02 104 | print("Turn off:",ppIn) 105 | ppOut = pc.GetReply(ppIn, pylonpacket.PPTurnOffReply) 106 | print("Turn off reply:",ppOut) 107 | 108 | 109 | if __name__ == "__main__": 110 | root = logging.getLogger() 111 | #root.setLevel(logging.DEBUG) 112 | main(sys.argv[1:]) 113 | -------------------------------------------------------------------------------- /pylonpacket.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | class PylonPacket: 5 | 6 | def __init__(self): 7 | self.header = bytearray(6) 8 | self.info = bytearray() 9 | self.checksum = bytearray(2) 10 | self.VER=0x20 11 | self.ADR=0x02 12 | 13 | @property 14 | def VER(self): 15 | return self.header[0] 16 | 17 | @VER.setter 18 | def VER(self, value): 19 | self.header[0] = value 20 | 21 | @property 22 | def ADR(self): 23 | return self.header[1] 24 | 25 | @ADR.setter 26 | def ADR(self, value): 27 | self.header[1] = value 28 | 29 | @property 30 | def CID1(self): 31 | return self.header[2] 32 | 33 | @CID1.setter 34 | def CID1(self, value): 35 | self.header[2] = value 36 | 37 | @property 38 | def CID2(self): 39 | return self.header[3] 40 | 41 | @CID2.setter 42 | def CID2(self, value): 43 | self.header[3] = value 44 | 45 | @property 46 | def LENGTH(self): 47 | return (((self.header[4] & 0x0F) << 8) | self.header[5]) 48 | 49 | @LENGTH.setter 50 | def LENGTH(self, value): 51 | if value>0xfff or value<0: raise OverflowError("Invalid length") 52 | sum = (value & 0x000F) + ((value >> 4) & 0x000F) + ((value >> 8 ) & 0x000F); 53 | sum = sum % 16; 54 | sum = ~sum; 55 | sum = sum + 1; 56 | val = (sum << 12) + value; 57 | self.header[5] = (val & 0xff) 58 | self.header[4] = (val>>8) & 0xff 59 | 60 | 61 | @property 62 | def INFO(self): 63 | return self.info 64 | 65 | @INFO.setter 66 | def INFO(self, value): 67 | self.info = value 68 | 69 | @property 70 | def CHKSUM(self): 71 | return self.checksum[0:2] 72 | 73 | @CHKSUM.setter 74 | def CHKSUM(self, value): 75 | self.checksum[0:2] = value 76 | 77 | def UpdateChecksum(self): 78 | sum=0 79 | for char in bytes(self.header).hex().upper(): 80 | sum+=ord(char) 81 | for v in bytes(self.info).hex().upper(): 82 | sum+=ord(v) 83 | sum=sum%65536 84 | sum=~sum 85 | sum=sum+1 86 | self.checksum[0]=(sum >>8) & 0xff 87 | self.checksum[1]=sum & 0xff 88 | 89 | 90 | def GetAsciiBytes(self): 91 | self.UpdateChecksum() 92 | ret=bytearray() 93 | ret.extend(self.header) 94 | ret.extend(self.info) 95 | ret.extend(self.checksum) 96 | rh='~'+ret.hex().upper()+"\r" 97 | logging.debug("Encoded sentence is %s",rh) 98 | return rh.encode() 99 | 100 | def Parse(ascii, packet_type): 101 | if (len(ascii)==0): return None 102 | #logging.debug("Value to decode is %s",ascii) 103 | if ascii[0] != 0x7E or ascii[-1] != 0x0D: 104 | raise ValueError("Invalid packet format") 105 | content=ascii[1:-1].decode() 106 | logging.debug("Content of packet: %s",content) 107 | bdata = bytes.fromhex(content) 108 | 109 | pret = packet_type() 110 | for i in range(0,len(pret.header)): 111 | pret.header[i] = bdata[i] 112 | if pret.LENGTH>0: 113 | pret.info=bdata[6:-2] 114 | logging.debug("Info content is %s", pret.info.hex()) 115 | else: 116 | pret.info=bytearray() 117 | pret.UpdateChecksum() 118 | if pret.checksum[0]!=bdata[-2] or pret.checksum[1]!=bdata[-1]: 119 | logging.error("Invalid checksum!") 120 | raise ValueError("Invalid checksum") 121 | pret.PostParse() 122 | return pret 123 | 124 | def PostParse(self): 125 | pass 126 | 127 | def GetInt2(self, idx): 128 | val=self.info[idx]<<8 | self.info[idx+1] 129 | return val 130 | 131 | def GetInt2Complement(self, idx): 132 | val=self.info[idx]<<8 | self.info[idx+1] 133 | if (val & 0x8000)==0x8000: 134 | val = val - 0x10000 135 | return val 136 | 137 | def __str__(self, **kwargs): 138 | return "VER: 0x%02x, ADR: 0x%02x, CID1: 0x%02x, CID2: 0x%02x, LENGTH: %s, len(INFO): %s, CHKSUM: 0x%02X%02X"%(self.VER,self.ADR,self.CID1,self.CID2,self.LENGTH,len(self.INFO),self.CHKSUM[0],self.CHKSUM[1]) 139 | 140 | class PPGetVersionInfo(PylonPacket): 141 | def __init__(self): 142 | super().__init__() 143 | self.CID1=0x46 144 | self.CID2=0x4F 145 | 146 | class PPVersionInfo(PylonPacket): 147 | pass 148 | 149 | class PPGetManufacturerInfo(PylonPacket): 150 | def __init__(self): 151 | super().__init__() 152 | self.CID1=0x46 153 | self.CID2=0x51 154 | 155 | class PPManufacturerInfo(PylonPacket): 156 | @property 157 | def DeviceName(self): 158 | return (self.info[0:10]).decode().strip() 159 | 160 | @property 161 | def SoftwareVersion(self): 162 | return self.info[10:13] 163 | 164 | @property 165 | def ManufacturerName(self): 166 | return (self.info[12:]).decode().strip() 167 | 168 | def __str__(self, **kwargs): 169 | #print(int(self.SoftwareVersion[1])) 170 | return super().__str__(**kwargs)+("\r\n> DeviceName: %s, SoftwareVersion %s.%s, ManufacturerName: %s"%(self.DeviceName,'?','?',self.ManufacturerName)) 171 | 172 | 173 | class PPGetAnalogValue(PylonPacket): 174 | 175 | def __init__(self): 176 | super().__init__() 177 | self.info=bytearray(1) 178 | self.LENGTH=0x02 179 | self.CID1=0x46 180 | self.CID2=0x42 181 | 182 | 183 | @property 184 | def Command(self): 185 | return self.info[0] 186 | 187 | @Command.setter 188 | def Command(self, value): 189 | self.info[0] = value 190 | 191 | def __str__(self, **kwargs): 192 | return super().__str__(**kwargs)+(", Command: %s"%(self.Command)) 193 | 194 | 195 | class PPAnalogValue(PylonPacket): 196 | 197 | def __init__(self): 198 | super().__init__() 199 | self.voltages=[] 200 | self.temperatures=[] 201 | 202 | @property 203 | def CellsCount(self): 204 | return self.info[2] 205 | 206 | @property 207 | def CellVoltages(self): 208 | return self.voltages 209 | 210 | @property 211 | def TemperaturesCount(self): 212 | idx=(self.CellsCount*2)+3 213 | return self.info[idx] 214 | 215 | @property 216 | def Temperatures(self): 217 | return self.temperatures 218 | 219 | @property 220 | def TotalCurrent(self): 221 | return self.GetInt2Complement(-11)/10.0 222 | 223 | @property 224 | def TotalVoltage(self): 225 | return self.GetInt2(-9)/1000.0 226 | 227 | @property 228 | def RemainingCapacity(self): 229 | return self.GetInt2(-7)/1000.0 230 | 231 | @property 232 | def Quantity(self): 233 | return self.info[-5] 234 | 235 | @property 236 | def TotalCapacity(self): 237 | return self.GetInt2(-4)/1000.0 238 | 239 | @property 240 | def Cycles(self): 241 | return self.GetInt2(-2) 242 | 243 | def PostParse(self): 244 | logging.debug("Post processing parsed data %s",self.info.hex()) 245 | self.voltages=[] 246 | self.temperatures=[] 247 | for v in range(0,self.CellsCount): 248 | cv=self.GetInt2(3+(2*v))/1000.0 249 | logging.debug("Voltage ",v,cv) 250 | self.voltages.append(cv) 251 | 252 | idx=(self.CellsCount*2)+3 253 | for t in range(0,self.TemperaturesCount): 254 | tv=self.GetInt2Complement(idx+1+(2*t)) 255 | tv=(tv-2731)/10.0 256 | logging.debug("Temperature %s: %s",v,tv) 257 | self.temperatures.append(tv) 258 | 259 | def __str__(self, **kwargs): 260 | ret=super().__str__(**kwargs) 261 | ret+=("\r\n> CellsCount: %s, TemperaturesCount: %s"%(self.CellsCount, self.TemperaturesCount)) 262 | ret+=("\r\n> TotalCurrent: %.3f, TotalVoltage: %.3f, RemainingCapacity: %.3f, P: %.2f"%(self.TotalCurrent, self.TotalVoltage, self.RemainingCapacity,(self.TotalCurrent*self.TotalVoltage))) 263 | ret+=("\r\n> Quantity: %s, TotalCapacity: %s, Cycles: %s"%(self.Quantity, self.TotalCapacity, self.Cycles)) 264 | ret+=("\r\n> CellVoltages: %s"%(self.CellVoltages)) 265 | ret+=("\r\n> Temperatures: %s"%(self.Temperatures)) 266 | return ret 267 | 268 | class PPGetSystemParameter(PylonPacket): 269 | def __init__(self): 270 | super().__init__() 271 | self.CID1=0x46 272 | self.CID2=0x47 273 | 274 | class PPSystemParameter(PylonPacket): 275 | @property 276 | def INFOFLAG(self): 277 | return self.info[0] 278 | 279 | @property 280 | def UnitCellVoltage(self): 281 | return self.GetInt2(1)/1000.0 282 | 283 | @property 284 | def UnitCellLowVoltage(self): 285 | return self.GetInt2(3)/1000.0 286 | 287 | @property 288 | def UnitCellUnderVoltage(self): 289 | return self.GetInt2(5)/1000.0 290 | 291 | #TODO: Doplnit dalsi neuzitecne property 292 | def __str__(self, **kwargs): 293 | return super().__str__(**kwargs)+("\r\n> FLAG: %s, UnitCellVoltage: %s, UnitCellLowVoltage %s, UnitCellUnderVoltage: %s"%(bin(self.INFOFLAG), self.UnitCellVoltage,self.UnitCellLowVoltage,self.UnitCellUnderVoltage)) 294 | 295 | 296 | class PPGetAlarmInformation(PylonPacket): 297 | def __init__(self): 298 | super().__init__() 299 | self.CID1=0x46 300 | self.CID2=0x44 301 | self.info=bytearray(1) 302 | self.LENGTH=0x02 303 | 304 | @property 305 | def Command(self): 306 | return self.info[0] 307 | 308 | @Command.setter 309 | def Command(self, value): 310 | self.info[0] = value 311 | 312 | def __str__(self, **kwargs): 313 | return super().__str__(**kwargs)+(", Command: %s"%(self.Command)) 314 | 315 | class PPAlarmInformation(PylonPacket): 316 | pass 317 | 318 | 319 | class PPGetChargeManagementInformation(PylonPacket): 320 | def __init__(self): 321 | super().__init__() 322 | self.CID1=0x46 323 | self.CID2=0x92 324 | self.info=bytearray(1) 325 | self.LENGTH=0x02 326 | 327 | @property 328 | def Command(self): 329 | return self.info[0] 330 | 331 | @Command.setter 332 | def Command(self, value): 333 | self.info[0] = value 334 | 335 | def __str__(self, **kwargs): 336 | return super().__str__(**kwargs)+(", Command: %s"%(self.Command)) 337 | 338 | class PPChargeManagementInformation(PylonPacket): 339 | 340 | @property 341 | def VoltageUpLimit(self): 342 | return self.GetInt2(1)/1000.0 343 | 344 | @property 345 | def VoltageDownLimit(self): 346 | return self.GetInt2(3)/1000.0 347 | 348 | @property 349 | def MaxChargeCurrent(self): 350 | return self.GetInt2Complement(5)/1.0 351 | 352 | @property 353 | def MaxDischargeCurrent(self): 354 | return self.GetInt2Complement(7)/1.0 355 | 356 | @property 357 | def Status(self): 358 | return self.info[9] 359 | 360 | def __str__(self, **kwargs): 361 | print(self.info.hex()) 362 | return super().__str__(**kwargs)+("\r\n> VoltageUpLimit: %s, VoltageDownLimit: %s, MaxChargeCurrent: %s, MaxDischargeCurrent: %s, Status: %s"%(self.VoltageUpLimit,self.VoltageDownLimit,self.MaxChargeCurrent,self.MaxDischargeCurrent,self.Status)) 363 | 364 | 365 | 366 | class PPGetSeriesNumber(PylonPacket): 367 | def __init__(self): 368 | super().__init__() 369 | self.CID1=0x46 370 | self.CID2=0x93 371 | self.info=bytearray(1) 372 | self.LENGTH=0x02 373 | 374 | @property 375 | def Command(self): 376 | return self.info[0] 377 | 378 | @Command.setter 379 | def Command(self, value): 380 | self.info[0] = value 381 | 382 | def __str__(self, **kwargs): 383 | return super().__str__(**kwargs)+(", Command: %s"%(self.Command)) 384 | 385 | class PPSeriesNumber(PylonPacket): 386 | 387 | @property 388 | def SeriesNumber(self): 389 | return self.info[1:].decode() 390 | 391 | 392 | def __str__(self, **kwargs): 393 | return super().__str__(**kwargs)+("\r\n> Series Number: %s"%(self.SeriesNumber)) 394 | 395 | class PPTurnOff(PylonPacket): 396 | def __init__(self): 397 | super().__init__() 398 | self.CID1=0x46 399 | self.CID2=0x95 400 | self.info=bytearray(1) 401 | self.LENGTH=0x02 402 | 403 | @property 404 | def Command(self): 405 | return self.info[0] 406 | 407 | @Command.setter 408 | def Command(self, value): 409 | self.info[0] = value 410 | 411 | def __str__(self, **kwargs): 412 | return super().__str__(**kwargs)+(", Command: %s"%(self.Command)) 413 | 414 | 415 | class PPTurnOffReply(PylonPacket): 416 | pass --------------------------------------------------------------------------------