{"id":49875101,"url":"https://github.com/epicre/zendure_ble","last_synced_at":"2026-05-15T11:45:55.181Z","repository":{"id":199362369,"uuid":"701032507","full_name":"epicRE/zendure_ble","owner":"epicRE","description":"Zendure SolarFlow - Bluetooth protocol","archived":false,"fork":false,"pushed_at":"2025-06-15T19:43:23.000Z","size":34,"stargazers_count":9,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-06-15T21:15:13.836Z","etag":null,"topics":["ble","bluetooth","home-assistant","home-automation","pv","solar-energy","solarflow","zendure"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/epicRE.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-10-05T19:16:26.000Z","updated_at":"2025-06-15T19:43:27.000Z","dependencies_parsed_at":"2025-06-15T21:04:15.188Z","dependency_job_id":"22597894-279d-4c3d-a3ef-725aa772be78","html_url":"https://github.com/epicRE/zendure_ble","commit_stats":{"total_commits":6,"total_committers":1,"mean_commits":6.0,"dds":0.0,"last_synced_commit":"9b9ee2174f6a3b1b54815f7fee7eba001cf96394"},"previous_names":["epicre/zendure_ble"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/epicRE/zendure_ble","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicRE%2Fzendure_ble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicRE%2Fzendure_ble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicRE%2Fzendure_ble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicRE%2Fzendure_ble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/epicRE","download_url":"https://codeload.github.com/epicRE/zendure_ble/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicRE%2Fzendure_ble/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33066125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ble","bluetooth","home-assistant","home-automation","pv","solar-energy","solarflow","zendure"],"created_at":"2026-05-15T11:45:54.198Z","updated_at":"2026-05-15T11:45:55.162Z","avatar_url":"https://github.com/epicRE.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zendure SolarFlow\n\n**Please note this is work in progress. If you have more or better information feel free to create a pull request! Thanks!**\n\nThe Zendure SolarFlow ([https://eu.zendure.com/](https://eu.zendure.com/)) is a controller and microinverter that can connect up to 2 solar panels (800W total) and output up to 1200W.\n\nManual: [https://cdn.shopify.com/s/files/1/0720/4379/0616/files/SolarFlow20230525_V8.pdf](https://cdn.shopify.com/s/files/1/0720/4379/0616/files/SolarFlow20230525_V8.pdf)\n\n#### Devices:\n\nName  : Smart PV Hub 1200 Controller\n\nModel : ZDSPVH1200\n\nName: Add-on Battery AB1000\n\nBattery model : \tZDAB1000\n\nIn this posting I will detail how to communicate with the Zendure SolarFlow using Bluetooth (get a bluetooth enabled device and start gathering data). (Without a local account, mobile app or cloud account) \"Some\" privacy is ensured.   \n\n### DISCLAIMER (progress at own risk! I can't be liable for any damage you do to the device!)\n\n### Zendure SolarFlow and Bluetooth\n\n**WARNING**: This system includes no security. It is possible to read and write data to the system without any authentication. (It is probably safer to keep a constant connection to avoid someone else connecting to it!)\n\nThe Zendure SolarFlow has 2 services, one Notify and one to Write. \n\n```\nSERVICE          = \"0000A002-0000-1000-8000-00805F9B34FB\"\nUUID_READ_NOTIFY = \"0000C305-0000-1000-8000-00805F9B34FB\"\nUUID_WRITE       = \"0000C304-0000-1000-8000-00805F9B34FB\"\n```\n\nThe following is a sequence of requests made by the Zandure Application. \n\n### Data structures used by Zendure SolarFLow\n\n\nJSON | Property | Description\n---- | ---  | ---\nroot |      |    \n| | messageId | STRING: This can be random; Used with method: error, getInfo, getInfo-rsp, read, write, BLESPP_OK, BLEGetVersion, otareq, otareq-rsp; Device always uses 123; App uses random 128bit hex-string, 1006 (BLEGetVersion) or 1009 (BLESPP_OK)\n| | method | STRING: This can be: error, report, getInfo, getInfo-rsp, read, read_reply, write, write_reply, BLESPP, BLESPP_OK, BLEGetVersion, firmware, otareq, otareq-rsp\n| | success | INT: Zero(0) is False and one(1) is True; Used with method: read_reply (getAll), write_reply\n| | deviceId | STRING: Unique device id used to identify the replies when you have multiple SolarFlow devices; Used with method: error, report, getInfo, getInfo-rsp, read, read_reply, write, write_reply, BLESPP, firmware, otareq, otareq-rsp\n| | timestamp | LONG: A timestamp (the device is not connected to the internet so it has no time tracking); Used with method: error, getInfo, getInfo-rsp, read, write, write_reply, firmware, otareq, otareq-rsp; Device uses seconds; App uses milliseconds\n| | deviceSn | STRING: Device serial number; Used with method: getInfo-rsp, firmware \n| | offData | INT: Used with method: error\n| | data | unknown[]: Used with method: error\n| | result | INT: Used with method: otareq-rsp\n| | reason | INT: Used with method: otareq-rsp\n| | packetCount | INT: Used with method: otareq-rsp\n| | payloadSize | INT: Used with method: otareq-rsp\n| | fileRange | INT: Used with method: otareq-rsp\n| | properties | See properties tables \n| | packData | See packData table \n| | firmwares | See firmwares table \n| | modules | See modules table \n| | firmware | See firmware table \n \n\nJSON | Property |Description\n---- | ---  | ---\nproperties  |  | This is used with method: report|\n| | [\"getAll\"] | It is used to get all the settings from the system. |\n\n\nJSON | Property |Description\n---- | ---  | ---\nproperties  |  | These are used with method: report |\n| | packNum | INT: Number of battery packs, 1,2,3,4|\n| | masterSwitch | INT |\n| | electricLevel | INT: Overall battery power status as a percentage (%) (e.g., If two packs then it will be ((A+B)/2), where A is the battery power from pack 1 and B is the battery power from pack 2. |\n| | wifiState | INT: If WiFi is enabled (1) or disabled (0)|\n| | buzzerSwitch | INT: This is the audible buzzer, enabled (1) or disabled (0)|\n| | socSet | INT : Maximum battery charge set by the user (%), 90% shown as 900 (value/10); App allows setting: 70%-100% |\n| | solarInputPower | INT: Input from Solar Panels (W) |\n| | solarPower1 | INT: Input from first Solar Panel (W) |\n| | solarPower1Cycle | INT |\n| | solarPower2 | INT: Input from second Solar Panel (W) |\n| | solarPower2Cycle | INT |\n| | packInputPower | INT: How much power is discharging from the batteries (W) |\n| | packInputPowerCylce | INT |\n| | outputPackPower | INT: The amount of power sent to the batteries in total (W)|\n| | outputPackPowerCycle | INT |\n| | outputHomePower | INT: Output from SolarFlow going to Home(microinverter) (W)|\n| | outputHomePowerCycle | INT |\n| | outputLimit | INT: Limit set on SolarFlow on how much to send to Home(microinverter) (W); App allows setting: 0, 30, 60, 90, 100-1200 |\n| | inputLimit | INT |\n| | remainOutTime | INT: How much time is left until the batteries discharge to 0% at the current rate of discharge. (59940 seems to be a default value when not discharging) |\n| | remainInputTime | INT: (59940 seems to be a default status)  |\n| | packState | INT : Status of the batteries. If zero(0) is not doing anything, one(1) is charging batteries, two(2) is discharging batteries |\n| | hubState | INT : If automatic shutdown is enabled (1) or disabled (0) |\n| | masterSoftVersion | INT: Software version| \n| | masterhaerVersion | INT |\n| | inputMode | INT |\n| | blueOta | INT |\n| | pvBrand | INT : Brand of microinverter, Other (0), Hoymiles (1), Enphase (2), APsystems (3), Anker (4), Deye (5), BossWerk (6), Tsun (7) |\n| | pass | INT : If the battery is bypassed (1) or not (0) |\n| | passMode | INT : Control mode for `pass`, Automatic (0), Always Off (1), Always On (2) |\n| | autoRecover | INT : If `passMode` gets reset to Automatic after a day (1) or not (0) |\n| | minSoc | INT (value/10): Minimum charge level that the batteries will go to. (They will discharge up to this amount) This is used to maintain the batteries in good health. App allows setting: 0%-50% |\n| | inverseMaxPower | INT: Maximum power that the microinverter supports; App allows setting: 100, 200, 300, ..., 1200 |\n| | autoModel | INT |\n| | gridPower | INT |\n| | smartMode | INT |\n| | smartPower | INT |\n| | heatState | INT |\n \n\nJSON | Property |Description\n---- | ---  | ---\npackData  |  | These are used with method: report  |\n| | sn | STRING: Battery Serial Number (can be found on the outside)|\n| | power | INT: Current |\n| | socLevel | INT : Current battery level (%)|\n| | state | INT: A zero (0) means it is doing nothing, and a one(1) means it is charging, two (2) means it is discharging|\n| | maxTemp | INT: The maximum temprature is calculated as follows ((maxTemp/10)-273.15) e.g. (2841/10)-273.15=10.95C. A better algorithm is ((maxTemp-2731)/10) |\n| | maxVol | INT |\n| | minVol | INT |\n| | totalVol | INT |\n| | softVersion | INT : Software version|\n| | soh | INT : Battery health? |\n\n\nJSON | Property |Description\n---- | ---  | ---\nfirmwares  |  | These are used with method: getInfo-rsp |\n| | type | STRING: Type of device, e.g. MASTER, BMS, BMS_AB2000\n| | version | INT: Software version or -1\n\n\nJSON | Property |Description\n---- | ---  | ---\nmodules  |  | These are used with method: firmware |\n| | module | STRING: Type of device, e.g. MASTER, BMS, BMS_AB2000\n| | version | INT: Software version or -1\n\nJSON | Property |Description\n---- | ---  | ---\nfirmware  |  | These are used with method: otareq |\n| | CRC16 | INT: Checksum of payload ([CRC-Type](https://crccalc.com/?method=CRC-16/MODBUS); high and low byte of result swapped)\n| | size | INT: Payload size (bytes)\n| | type | STRING: Type of device, e.g. MASTER, BMS_AB2000\n| | version | INT: Software version\n\nKey: INT=an integer number, STRING=a long string of characters, LONG=a long number\n\n#### Example communication of reading settings/values\n\nI have anonymised a lot of the data in here. \nDEVICE_ID, UNIX_TIMESTAMP, DEVICE_SERIAL, BATTERY1_SN, BATTERY2_SN\n\nIf you receive this in Notify:\n```\n{\"deviceId\":\"DEVICE_ID\",\"method\":\"BLESPP\"}\n```\n\nThen you can reply with the following:\n```\n{\"messageId\":\"UNIX_TIMESTAMP\",\"method\":\"BLESPP_OK\"}\n```\nOnce BLESSP_OK is sent you can then start sending requests.\n\nWe can now send getInfo:\n```\n{\"messageId\": \"UNIX_TIMESTAMP\",\"method\":\"getInfo\",\"timestamp\": UNIX_TIMESTAMP}\n```\n\nAnd get a reply getInfo-rsp: \n\n```\n{\"messageId\":\"123\",\"method\":\"getInfo-rsp\",\"deviceId\":\"DEVICE_ID\",\"timestamp\":0000000,\"deviceSn\":\"DEVICE_SERIAL\",\"firmwares\":[{\"type\":\"MASTER\",\"version\":XXXX},{\"type\":\"BMS\",\"version\":XXXX}]}\n```\n\nNext we can send a request to getAll information. (This will generate a number of notifications, so be ready to get lots of data):\n\n```\n{\"messageId\":\"11\",\"deviceId\":\"DEVICE_ID\",\"timestamp\":UNIX_TIMESTAMP,\"properties\":[\"getAll\"],\"method\":\"read\"}\n```\n\nFirst reply to getAll:\n\n```\n{\"method\":\"read_reply\",\"deviceId\":\"DEVICE_ID\",\"success\":1,\"properties\":{\"getAll\":1}}\n```\n\nNext reply with the number of batteries and their serial numbers:\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"packNum\":2},\"packData\":[{\"sn\":\"BATTERY1_SN\"},{\"sn\":\"BATTERY2_SN\"}]}\n```\n\nMaster Switch, electric level and Wifi State:\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"masterSwitch\":1,\"electricLevel\":90,\"wifiState\":0}}\n```\n\nIf the buzzer is on, max battery level and energy from solar panels\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"buzzerSwitch\":0,\"socSet\":111,\"solarInputPower\":111}}\n```\n\nSome energy values:\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"packInputPower\":0,\"outputPackPower\":111,\"outputHomePower\":111}}\n```\n\nWhat an error looks like:\n\n```\n{\"messageId\":\"123\",\"method\":\"error\",\"deviceId\":\"DEVICE_ID\",\"timestamp\":3034829,\"offData\":1,\"data\":[]}\n```\n\nOutput limit to send to the home, input limit and remaing OutTime\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"outputLimit\":112,\"inputLimit\":0,\"remainOutTime\":480}}\n```\n\nMore examples:\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"remainInputTime\":59940,\"packState\":1,\"hubState\":0}}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"masterSoftVersion\":0000,\"masterhaerVersion\":0,\"inputMode\":0}}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"blueOta\":1,\"pvBrand\":1,\"pass\":0}}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"minSoc\":113,\"inverseMaxPower\":112,\"autoModel\":0}}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"gridPower\":0,\"smartMode\":0,\"smartPower\":0}}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{},\"packData\":[{\"power\":111,\"socLevel\":11,\"state\":1,\"sn\":\"BATTERY1_SN\"}]}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"outputPackPower\":000,\"outputHomePower\":000},\"packData\":[{\"maxTemp\":2800,\"sn\":\"BATTERY1_SN\"}]}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{},\"packData\":[{\"totalVol\": 1000,\"maxVol\":111,\"minVol\":111,\"sn\":\"BATTERY1_SN\"}]}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{},\"packData\":[{\"softVersion\":0000,\"sn\":\"BATTERY1_SN\"},{\"power\":111,\"sn\":\"BATTERY2_SN\"}]}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{},\"packData\":[{\"socLevel\":11,\"state\":1,\"maxTemp\":2800,\"sn\":\"BATTERY1_SN\"}]}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"outputPackPower\":111,\"outputHomePower\":111},\"packData\":[{\"totalVol\":1000,\"sn\":\"BATTERY1_SN\"}]}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{},\"packData\":[{\"maxVol\":111,\"minVol\":111,\"softVersion\":0000,\"sn\":\"BATTERY1_SN\"}]}\n```\n\n```\n{\"method\":\"report\",\"deviceId\":\"DEVICE_ID\",\"properties\":{\"outputPackPower\":111,\"outputHomePower\":111}}\n```\n\n#### Example communication of writing settings\n\nI have anonymised a lot of the data in here. \nDEVICE_ID, UNIX_TIMESTAMP\n\n\nSet output power limit to 30W:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"d9f644656a4346c8a459aadb8dcfd92c\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"outputLimit\": 30}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"outputLimit\": 30}}\n```\n\nSet minimum charge level to 50%:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"a8037350118b40f28ba2aecc9649c305\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"minSoc\": 500}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"minSoc\": 500}}\n```\n\nEnable automatic shutdown:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"30cc48981dd14a56bb2aa8512f34b39f\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"hubState\": 1}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"hubState\": 1}}\n```\n\nSet microinverter power to 400W and other brand:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"b3337c1f052f4c3783e3bb3678adddba\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"inverseMaxPower\": 400, \"pvBrand\": 0}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"inverseMaxPower\": 400, \"pvBrand\": 2}}\n```\n\nSet maxium charge level to 70%:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"222497e9c3764d22b2b65e15b99fdfcd\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"socSet\": 700}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"socSet\": 700}}\n```\n\nPassthrough:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"fe2c5da7f4464ba7af80675e63eafbf8\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"passMode\": 2}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"passMode\": 2}}\n```\n\nDon't reset passMode:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"67d9d1bc2ca946e2b4c57e47e7c3d75f\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"autoRecover\": 0}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"autoRecover\": 0}}\n```\n\nDisable buzzer sound:\n\n```\n{\"method\": \"write\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"c48542da7f55414d8e97fc717fd862fa\", \"deviceId\": \"DEVICE_ID\", \"properties\": {\"buzzerSwitch\": 0}}\n```\n\n```\n{\"method\": \"write_reply\", \"deviceId\": \"DEVICE_ID\", \"timestamp\": UNIX_TIMESTAMP, \"success\": 1, \"properties\": {\"buzzerSwitch\": 0}}\n```\n\n#### Example communication of a firmware update\n\nI have anonymised a lot of the data in here. \nDEVICE_ID, UNIX_TIMESTAMP\n\nStart the update:\n\n```\n{\"firmware\": {\"CRC16\": 61491, \"size\": 76260, \"type\": \"BMS_AB2000\", \"version\": 4113}, \"deviceId\": \"DEVICE_ID\", \"messageId\": \"883e4ea9bf0b45a09906d9e573d3160e\", \"method\": \"otareq\", \"timestamp\": UNIX_TIMESTAMP}\n```\n\nThe device replies:\n\n```\n{\"method\": \"otareq-rsp\", \"timestamp\": UNIX_TIMESTAMP, \"messageId\": \"123\", \"deviceId\": \"DEVICE_ID\", \"result\": 1, \"reason\": 0, \"packetCount\": 15, \"payloadSize\": 128, \"fileRange\": 0}\n```\n\nNext send a block of 16 packets (`packetCount + 1`?) with 5 byte header and 128 byte of the payload (payloadSize):\n\n```\nHeader        | Payload\n55:2f:00:f0:80:e8:46:...\n55:2f:00:f1:80:d8:81:...\n55:2f:00:f2:80:9d:a1:...\n...\n55:2f:00:ff:80:63:5c:...\n```\n\nHeader-Format: 55:2f:XX:YZ:NN  \nXX represents the block number, starting with 0 and counting up.  \nY represents the number of the last packet of the block.  \nZ represents the number of the packet, starting with 0 and counting up.  \nNN represents the payload length.  \n\nThe device replies with:\n\n```\n55:24:00:00:05:20:00:00:08:00\n```\n\nFormat: 55:24:XX:00:05:20:00:0M:MM:MM  \nXX represents the block number, starting with 0 and counting up.  \nMMMMM represents the total received number of bytes. In the above example this is 128 × 16 = 2048 = 0x800  \n\nThen send the next block:\n\n```\nHeader        | Payload\n55:2f:01:f0:80:56:52:...\n55:2f:01:f1:80:04:d0:...\n55:2f:01:f2:80:08:40:...\n...\n```\n\nAnd wait for the device reply:\n\n```\n55:24:01:00:05:20:00:00:10:00\n```\n\nThe last data block may contain less than 16 packets and the last packet may contain a smaller payload.\n\nAfter the reply for the last block the device sends the following packets:\n\n```\n55:25:25:00:01:01\n55:26:00:00:02:03:00\n55:26:00:00:02:03:02\n...\n55:26:00:00:02:03:60\n55:26:00:00:02:01:64\n55:25:00:00:01:03\n```\n\nFormat: 55:25:XX:00:01:01  \nXX represents the number of the last block.  \n\nFormat: 55:26:00:00:02:03:VV  \nVV represents the progress 0 to 100.  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fepicre%2Fzendure_ble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fepicre%2Fzendure_ble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fepicre%2Fzendure_ble/lists"}