diff --git a/extras/.DS_Store b/extras/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/extras/.DS_Store differ
diff --git a/extras/stubgen.py b/extras/stubgen.py
new file mode 100644
index 0000000..c31a0bb
--- /dev/null
+++ b/extras/stubgen.py
@@ -0,0 +1,161 @@
+import base64
+import zlib
+import json
+
+stubs = {
+"esp8266": b"""
+eNq9PHt/1Da2X8WehCQzJEWyPR6ZxzKZJNNQoIVwSeluehtbtunlVygM6ZJ2YT/79XnJsmeSkL7+yEO2LB2dc3Te0n82z6rzs83bQbF5cp6bk3OtTs6Vmja/9Ml5XcPP/AwetT9Z82Pw7f3mgZGuTcMo+pGeJvHb\
+06n8d7jLH2SJGwp+ZzSljk7OLbRVEMDcUd78ipue45PzagLzNR1ygK1qhkgX8PZp00rgcxg6hX+0PGkGUWMA5KvnzbgqAAh+hG9mzVRjhExRX13sAZDwL/ebPYfft1P3YLCHv+XLZpKqoEnguwa4LLofNg8FBPqn\
+AarCxd2OuiA8lR4nm7AUWrtJuwiXH/5w2PxqIfwOhpkDljqdvut0gk/iBpoSYb3dgK8s4ZQ7REc8DVBD8N/wwgKoQK9ybDkmMD4TCEdU97853H1AnJRbfpsnrrHVLFfBwA2WK0sUxwaCg0OfCtt1165X4AOwv+qZ\
+sV02pBl6HdtJei95IYQ/12jm3/RGTFaByyB3Fq+MN0jRedPZLGbY22C1P0DMDcCCa8BIbrTM8pao78MIpexI4x4TzXTRQ4VpV+L2+ZPmV+U1dCSNux6YhfLmLxKvUUIjx8Yd74O6IzisDxkMVXlSRBVd0ruX2FNW\
+t5IBVBdC2qcMgO4yVubTAxu5LMcKPaeERNfI28YLpOJ0f45/th/hn/NDx1Nf8X9F8oD/s/YL/q80Gf7X9C5laFhbhUuaPtqQufnbkGAC6DOQfrQ98ROtYRsP8rUBblJaXZQ3gspGeSPjyigH+RPlINqinPFWsbC1\
+Dl8wRcRiq4gZU5b2gUp9bANI0cPBBHo36LBjoonSDAFsQGX3RuEBQ6S5EzzX4UeeWf/Gs+UolkbbThw1/wB6opDw3UKCT7X/9IiGL5eWA71QtA4IXQgEDQ8kioP1oCsfEfiAh4v7w/Hz6HOfv5Nd2Ij4rGIlQP9o\
++adgyBQvL2IoyxWkyZD6mjC15iA39JlO38s3jMCS3/QEfdY+1dFgFxhsgHId4LDr+GQ8e7oX5YMNZLVGKGgbT6B7wKrJ+LuMvo4j/APKC5WjVoOgBv2qt3bc3FvQY5APuuyk7WDwdI8ZRPlcBBo5Z11lWP9nMfVY\
+0Krq/rwkZeooaFA2YcFcT4izdcwjW9Uwpqm8uaqKADAlAzYmGindbO7S+mIjatGFdN9koCJaVobLmkRf10xRm5IgQgGUvg/qWJ5X8hBsClOvyXOUG3MSj0rVezL4f7D5jNebkpJANZLSHhJGypagIpMCNmwz0fsW\
+MGO9EbBPLR/OiQIyZuGNOf9VIIyEhp0ZbWpouq2aRAkBPJPO8KCB5Q2NXPeg3eFJAZl5+4nudDPpQ8c+HmCWAcvGbKYBcUsNPXS83cx5Js8UPWt+DKEi81iauvQmXPffxd6k/wV+AXR5JACILfyFPZk+IY5CSkxk\
+mg0moJAiEVJIb/3LsrCpcxo3a5ZNn3V0XrC9Cx8u+h8eHxEoDa5R3+AmUdvUxdpbMO13PK0K0Bw+JvSAjV3pCQ2IbBRjH7B3EchXS3ORwaBLMvbkpZuunvyjeZN3RiOw7YqhQNuh1FuG/Fi8gPlXDLolw8VGss8d\
+juc/CXalr2JuL2oh8KFQ6XDpVUKvbMoklhmU3mi7qRTZdQAMnOg1WkwVtVrZwYWcVbjd8gNK8u9RVI7fsRJIt31AZzIfgqUinCMEAXnZNCz4aJZjlMH3QOuBMI1gwhnqn/HPMLImVV/XsxDtAximjG+CLl4LviYR\
+DKoJ7WISUiHxB/wQ7iPPEREoUvLa2o2EFo2BxZojIqx1hEVN5cQ0D5OB4IaptbkHgxQstGsanTTJTJgJ+CWeNs7jnvDbOph+JLfHR/iHlR7un5j0Bnl1+sleMI0Cej1pWQrViwK1FmzAsLI4zejM4nniaetqfE2s\
+2PQWYiXyuhjqovuSsYpYkiL+VHqzFZImakFmMbRJO59G2GrFPfheaNqp+huW96jk0NoLeztAiUs69lHudtE3rU5yYyztRXqvq86XMgXMXra2pkYF8pR1r0PkYbvPdeyzzg5NVqs1QOn0H/Ab6bkK5dFRs2nKlGUb\
+zBqd07SuK1iTk8kBjCju3or1gGwitvQoVeFQ6CrfHqLJ8zBGl/3hvthditQAMWdMMKCyADRAPECh1Q/2SNbq7yoRgjHmTN2ivTIt2lHBCrjpspu0Slaw7Qgu4Ph/2UPAph1/LyYR6Gt9TGPbycC3g2qyEBtFC7tp\
+DKZfx/ZJwNYVWAsgYbIRtX10woyTkkRzrp9dmxCapSd4aCqJREPjrF+ygUAwMDchO9RzGS9sI0YVCJESJSgb3BgWqXoGN3qZVdfQDtlfrII14q0KmAw2XalAsBohmUY5tcN00OQSd6cAIRcz4TQaKb0OtGAnX/HZ\
+EQw5nqH0x+HjrZ2D/2M6pGQeUG8Sd/GgtY9RzgVxEJHEqpUTqgeHonOAkv58OKd0O8rXL7b2B8RLzaj5iNkQDYZfWFFluL53xA8wQRGBRFX5I2oaYcoY7TkYPIvCEbzIUVsyPhr8bGYer+dghVKPCC3Y8W1aTBGF\
+6TvaaHV1FK4X4ejtQzZ48v1XzynIYJKj/AZOcpNts0SY7WtofGgGQtadP6Z3ECaA/ZoD+jUSRI+3XgO0+RbCsnGUD+8+A+n8CfbaDrO3QQm36RvEpMJUrP8NMJLa26KXiIqI/NgTXwSKDCEPHtQH/DUgYkr4hTzV\
+AMHytMLWFmvuai6ifBEilnHbvMU91XjlNjndgzFfo6tdwMYvFuH6nMMSrF1NFK4jcW6EjzkGO6HYVok71YgSPs+IrGjnsQLJI554Tb0C/H0kvwsBSU7BLevMTh+X0CNuIDlq5pzBnN82E05g4x6xmlaEYRN9R/LC\
+qFuk1tGojP85aCydI4hEEpeARVlOYMN+aIWYjhvL5yhMiHfYjGUXMQieA7jHtMcpmhUkwvQmGHNgDk3nXfIFJGyho/yw3VS6nvKuyvW3aRs2VDXGnhW7kJY3NOIgVnUANv+83VlqhWAvG8qEQPlqe88hEP0oIL7i\
+B0V6N+DvO0FWYAkgB/ODwH66JkRB2yJmKZ16I8K8c89HKVlpNSxCm6ooScgi2uFtsTNobSKS9FMRipqwZ20AfnmAAIP2AxDqd0QVlOMqWB8CHwTh7hA0EvkbVbxLVAGNWpfHMwrzrJ9s3h2xtumhLYtk5eAFXrb4\
+ZyGp5brKik9iQaKvGftDXo6WHNGSC1r070ULL4o2JzY49DdlRCArDVjxmYBtDEV2kxgAQMjMelECe32e4M8r9hJh3bJ7OyyAsf/PWquskVYMLECxiioR47r0iAyiFQTLeY0kh/CYs5ERE2Z31eKY8q09ycQnSZjI\
+EoG09j2xTpksLRHIGVK8DYBydC13SbzXZSSJkzoffDUQwAQRmiVFnXpWNIWcQpYB1UdOakXsWoPzTz52UKtTyEEkLxvwaxKG1jOzHaDbuIJeroPQOmDkXFMa3GVRUOACPJ5nq7OfcFktFbLfLxWEJV4T7Mjv6kw0\
+sSN8h00QCJcuu7vH6GaV7LAVsjgnhT0mSjaUBu1c/Az/HggVn9MeQgxOjjlQa9jZsV5QLmYDBvAZXSYiwl87M+e+Br2aLNuW41mmm+y4cFeWoMH1G5q9hLykVU9AoKqXHD7QOPBL9tWwVT8miMDufXIjaFRrkY+d\
+DSYGb+nbs0U4dsr1FVD81fPTnzGOA9yfzcmXAzIQGtBBMHPf6emsAjNIccgmXokSec6aKea9Xm+ISwBICVr5MngJegCjKhAqhkBjXc7gYSTCcUyLWoBF1a6rqPrrWoQxLYoWCJb/hCMYmYvbmNneCwB7gNEJCMdR\
+YkK9penOwItBX4C6fKAuxEJZsgCgXwCdXjN67Pnc3yu/DWAR6IygfNkhJOgCxgXWq3VI1lgNGscIli0HHtswihNPA3a9IHxu0lNM3XBQvURZ1m5W0EK6nPdlCJt7rFrzS1Ur28Xps56ADc/cfEV44+UqW+Je1FXA\
+LDVgSvTRqsALwJAQmaDaDH+CsXd/cjN4/cTKxgHjVgxNv2GKAp0qijEoEjCOJeKOBj7EcOABaqZ1BYlbNbBsnDrZf9+T/ei5hj8gR7IypCyOZ8Binlv1WZFsEmR8t9Xm3RwoW/COKuYKqlQi5VFELtzuy1CGoOU/\
+Ec3+AV9tXkuvo2kXPkRCjFawVjO0Wh/th4c9nd/gsqPiweojdIZrqHQHGBG4T8Jd2xiihqDkkWcmKMPmLKGR8R95gQWE1up9X6yhHFD6Xyu3P5ZNoFmCyzzlWhjYOdFuiuBu4cKElOw5aPRZMI2in/VHRkJFEIwl\
+RsBqmovHUfrx8giQKAWxCiOBNzFC9uNUmeHgceO07gzyLx4z3zRyzMk01uUx40TVL1qu1eSxRgNhZsKxjsIvprcADMg3VWJ2RUN4NyR0Y7o4ai2vSu3RIBhS0/tfUXwyw2T7V7ujIccreJ4h+dQgtfOcc0zVmCMo\
+tYUCF8P7t05Ho5ASfLWdUZarVNMv2TWF6cwLCDMYltmFHa0jUbYRvDOwveJZng0ohrh3lYG/zXo+y5ZE4TZO+EZoC6Yimg6GHHUNzn+uzjmlW5DorTNPKZo2U8z4GORmFpq3AQa181sEWF1PB8Hbd7vHP7ShA5jN\
+TCZ33p4zptUH1IcfoPn2rZ6FaoHfY1Sl8bNsRQY17mHDliPE33IN+EreEuQZV3ZA6ElD5imPvWSRkwOz8BZ8PZi9aBNZzeebJD9qjDEHtJ+ygLYPVofltI1y3pmF2uXiMEvAGDMVOBbMMJh6exnBRPYThaso4lUF\
+w10yJdFmtewJJS66hKTh2VXSbmLbgJ99ZFWIzxSDlnBZi6EXuVoJzAcCRr13wTeMeIUTtsdICkxmIWccCtP4tDQ1IMGaAOkbfABQPqIACLO3v6nfFgOBf/Qh5IIHC90y9G6+heFmwHfATRna2vuzfHsRfkGyGxJ7\
+LlDLZQcNXDsbW15GO2ujkcYrV4Awsu1kmFqv6gZssiGJojwVaZ5uSJ0Jo7/C7hAvASe6tqO1Kdgxit0bctcijDAGmgrkkFOUGoIgSCN0x8H6UNF8vc1taE/9kJnIoMb7FF7BIKT9SJa6p7NP4TU4VA2yt9RoA40q\
+B1MmEqt4TwkETLekNy3afC+pRAf0y1LE6zNMIC2FGhAy9SOwzhTy7J9qID6lLu5drWaFXKj7EBdeSL3M2+D7qtoO9LzudiwhrEBiEG7ysH0Q1gIXqIgk9BBShYVH+vwl0f0UcRz5dI87dKdSA5VpGNYk85Oz5bI9\
+3I283Dzh/aoLcR8SJDEUWmmsRU2Ck02Fwj4fnHZp/JjwlAHrOlp0zTpn9A3A26QkyMkm5AWSEGx3uwi3KC7xb4JKancWM2HUNqCKhRKGwtng+0/I8nfOrB/t81wgSnuEXwozp1kxhyBipX8itLZReZHFg3yDzHWK\
+rS6uy6RQpZJydnMFZ14U4lmEG1e59B84lNWgY0N/RzjLyOW2EnB0mqim7zM0yora5TQAAvD50EH0g3KmQ7xRHT6lGYyj4+iWs/6AGtnJQt9gLQHWiSH7yt5OnLuBPnDf9WXR1vd7uUyCLXJfOFFQMyP7BLK1zRDw\
+J3pVvvQzsL+21UwZBhs8B7DDE89IAFkuiav1bRg7vAO/76GNt2zWft2HurEAIZJTU60CuMoI020sH3zQ7+07wJwqqO0WEaXxlIZMHi7Dq+05WaVl+pL43qb3EHegOJywUqkoEDC6ql02SYoniTxfE9w89rdURy/N\
+v2AObzvDHGBGQP0K5CUrUPlZ0lE9xQrVQ+lCVj0zT/W46iCOvUW96FdXA7HWyTzT46/UQJ8TwtWd+Gb5WUGjP1MDFX+bBtqluorLyY6GNRQ4KaY8FdoFQykv5OpE68OLREadU7SE1cVah7qkOdi6xKEmbAvXMUqI\
+Kdf4cpQeQ7pIXGhAjYjTRt0wf+C0UZvPr9luJjRPW/Oo651TqFNJGFT1wqARYssLg9YU++SoKwZAsbQi75uGwFgZl0s6bpE89FpwEdMwOVuaafYpWpqhJ6GWTUUinNJIKdgernbGEk10sX6RMaAydRstUWYPk8fT\
+mI0SrNJBoyBiKzTmUEXpqZkLKPIBEPyRa5awJmOCZQ6zJczsI/jrwcXbqeSKExX1mBqqDvoIIs6e+QjCGaaBnj4QBEUegjAxKtopXRJHw0Yc5f8V7PRNptecH2cbvS4lRO6hW3Az9XCjOGeMjIT4jOZdFm1wAEPp\
+m8/nXOVcA1wmqSkt4upKJEpBpwVGtQR2ES/Aq/rVgIuQN0KhdGOZvCrYEeDEJnhGavzhLhUxolSqfl8ETXPgX6UcN/IEqhdBuzQ31thmG1enQ3IKaRbY149dpv43ftTytBe1TLsWUpu/7jp6czYPbkgRbcfHM6xd\
+H62WscC/qr7Evauu5d51FOsBgP0zs6ecEKv/avfuOpyAsEK53gUqtssRf5KKnf+t+jXnOuOW9qdd2l/i4pmui9fVr0bv07B/r3OHtnMhe0aPYoMBpXxDs5qA3TlaY6myhfLjDef5YUfqai7S4/MMMRJjWIk1aeNv\
+V9tj5UXiI/ss8ZFN2PtiaeNLkLz9rKAMaCtERrTB3nSwxxWSiMAi33rHAUsCeuvtL1I9Gd6UQ6Y5OmIsTEipq9uDRT68RyKAiwaprG6Xkr9oNeWIOTzhkfzEEjySIlw0rbafZVgFg7G9u1DBUWKO4dPJAl/YDaoN\
+vzQjy2eUcmcHEaaGDT4W4c3TEbIKH6rK3xJm1jwiAGkajXwgEj0fssJTxc/HvGVEsBS8/+jbgpmB1ak819h02ju6M8AH0R1QomlKeRuIgdepG4pGgJSoGgtsmlTrq+d+fYqEWj9S9Nk4T2XRHlhyEcPKPz8GE8Iu\
+QFUxYXEuccRUvEasWwm8dOHnCdFttrVSrsK0n1dv05Oq2V9bb/MRVuNqbSgl3anTgiyZX5LQsNE6VBnmvEu4rAHztSVysJZ/HuOBtzxxKekDti4kHwlOeOO4N054QCkP7WIMIJkeXVTmcw0KZLFfO/bn5wOlqAO9\
+pbMuqvxanjfXCVxtc6UAJq/zrFhd8NKJZU2knHEiO/jD7q+c5UB1uiUZOamAHdIDquPhMhwUNbUMHS5QIK1lRVvgZ1xkT3phIG+DLFGdPITjBhat8vgCDjLMQabPQaA1f4QpxdjFkJnhU0TZufCTVDlQJpICWXga\
+LmnTkBKLY/0A3AaJgQojGeGQiRPxcd3oR/gHs4jA3y4ZXYRjPucP5MRe4DPgP3z+r+TKeEzJjLlKXrYyHmLBIOGYFQCUnRoptB5/ak8ao/RPHkuyjH+wBxpL5wgLxuNu9KvCfONbJCx5uFh0G7Wl4/AM8AB+BuYY\
+uQDbCb6Y3skPOvc5HS50fZJL3o0veZde8m7SfQewVdw2xeA2rOLLDLA7XQN9ClxcMNYzddrxwCJfj8Gn7WCjjKSqir6EFGytIZyf42mMWWMtLPHVAUbt8dBSLmcvdmgn2vF7yhNql3yefuBzIw3/7f4TviH+YQcY\
+z/lPdriqFQxFK8c80uUbBDBjrcOQp84pmAH0LKOWw0p7LGKZ2ajinFvFJWaYA4yXXFQvnstegpL8Iip3DB7nR+Foa62AwGqJJQZ4EvE5/wMdS8iQ1/lwzaDtXdw6ykfvwOrALZs/C24FRb7+3ckigFTt+O1YjvAB\
+xDMK4RgsLsbTYtMbFN1Br1aOIGkqDyQ6gUIeb7lD1wvE9ojqUhVvMpQmZusejVNEssluaTlGAOCjCyh5Qo7XlC54Jme6JkMQcukd74gfdqhgeD4E84pLHOQUVHshhjzMYBSbojiHY3OS7VdpxfIONgDArrD4Pu8f\
+2Z4sP8QaJ3xoBBQspftfgcad1vhi+WOrPJ2Kzr6Ew7Tq9w64JIPtfLYsm2l3AM338HxjAD4QODfuXJDrlsL0BZ3+GPApFjOOxGtT/iFUOewXz8GfL84//fj6xfeHj829rZ2WWUGOrUJI5RdpRMT9VqJhoKHQHdN8\
+7LqPYhkcsvrYUW3tIGKXDqJjTKx0R6fYgRQDPjv83uw84NM3WBuVZa38o7te5KBYJvcEGD6q5o7SwS+s6JVS3exeC6O2eNwKJ3EdGRZF0Hfi1TULDMvH6OpS9gaI31rzcT5kgSjc617ZYKMQL2UI8VKGEC9lCO+T\
+lG4Q6d240r+foy0nhUYrdk7961FOpQy6c5EOCTrvCgRm5ejkDJ5lIRvGYHTWpneVgGXDAgUNFthaur6l1HxlRNar0sTbF+qkMw7ImMZ7XuCdPsgpzp4BLpdYE1e70schR5xrvpmmBZ5ZwBqmJ7CgHKVafX+N0wNy\
+MYLDW+RjNFpCL8ztIdU/D6bU+vEeA0q3F+2yCVB3GV2zO98+ftC5lgFvpDg+W0KYK5GiAhA1WFqSWbr3ZNpeSeN0YOKv2eMKG7HbAQ+MkWojvHBpsE0farxhCQ9D8sq0eNkTwiAmS+zYu8EpLYR8cecupv7295F5\
+jMFd84g1p5wKinblvCqkM+BpYbafxLyZLNT9iWFDNwuYbTneWU+fyfUobprU0czdzgSSQsNhUzxY0IVxLM7XZ1DOPArwefbIbI/WhtvCtMA9q3jykVwQIy9RwIIcsOhiADLx2JwBpzPbPTl7B9v0aSuZMe4Rc1KV\
+w35aPJuEd4alEg84PA8rKe3u6tNDSEAk19YjTgagcbq1DfYHHoeM+BKuquUDrBAGZwgPWqlvwKNT/+BrVKRjR83aSLR4Mjw5wS8P77KOrSFDYaFyqJHJEJE0UIgGlValecrOX93eURUIMv37l5R6eshXS7WcfiZn\
+vJIZRkHUo90HN9xmhb7j4Rjz2MlXg9FasL07PJBau0ElFzL8RuN29JzRomTGwRopx8sEkLrTIzoC5w4Vc821lotJSlaxddHHpHSwS7LXswTKQs7Pyj1t6UXj8M0n7T7trPEqqdoRKq06IfWC3HoblF7HbB60JpO4\
+s3JTkEgacAYzKq0hAwB3Hv4TtYL/ItBe+IrxbfcSMYjfYoEe0sLibSLTxHuGnmAmVzN5JFjarbA1cae6/dleyyR7dcAnvRSjQzbteMUVH5qgQ05Lpc5JPArMddR8xqKkkxgLLJ7gM/BKt1uiM6yNHgN6tNKBBw1u\
+03K5e8lOY+kiuceQ0ynl/BXex8Fj0C09rIGXbi6qIk86x9j7QFa+0wZCuuIIudIS4suq9WPx7N5nCoUuX7pLJUgWSBHodVn9J5+NfvQbZ37j3G987LKe6d1nl/Xb/n1jxt5ZoSUy3RokpCNK3iBWzAzAFjAjMCVy\
+qM+UDQ2cXYLk2eb8Qx6jettteajsEHrfu/tEHZWye1/zOfuVvJeLhg75ChcsdKvhAsT0pV8/ekgnVzglVUxW3VCWiOpG/hnttjDP7/l0yrnIE/qWbArJpPgN24sxXxODW9UlOUGLRUEuAbx7bFjm9XdSbYS3Tdxi\
+PZevun2sNDJr/IIvsLQcUEIfZfaafQZ3p0q/dGi3vVjMmTj5Pl7teCC32QFo1U57a1XF8hsDZIgAhJZXUKodBjmzK8hk83Y2dDxzqPfH/Ec2FgAohY6ZEIjYAm9gcVf9ZFuYi7Jkbcq43cMmlbXIySg1e/xTSPFv\
+d6jAyKVxasWNQGj4gIKgTwYnC9yTMDhZeAfemXDOQaCHiif6oYysiBwyAZqELvWkwG7Fl2YQdt1FoxM5mrDx9TD8xDsD73zoAGyWAD4QqRieL4lB0nVSfyvXm7rNiBjg0oRuoEJuBsrRZMY7CPLungLn13x7t3WU\
+3QV5qedTSbZO/x4BC3GcaCZSFd27L/k0NODQepEDP2HuDoMoqJaqIhpMzumNN/iUh3WpsJmYMnj3BU4BX9vBsM0HkZvsTCK8nGvcnpfQNB9em9GKdBm4hIh7JLAXnU9aM8x99lGunas7J2nXsCIqegKMhif5Yzhk\
+UoyHT/Zb8umkPZomqJjghS+KuUYXA3e2Q2kw1Yv9Y4pJNFzIehI6eQAYqjLBJ5PUi2tEq0w1+tl/cNxeO8i9mkVsAfyRDz/Ehi5aAgVFKALYADtjOOtjuUrK7yCzWChQvRyQPrg4JzhrzfBnQSznEChNt/RN5GO3\
+fb25HeBtxz+8P8sXcOexVpPEJJNJkjRvqjdni1/9h6Z5WOZnOV+O3LnIFXff2LO7pSRYLneL+afga4zwdiBU5xOvUYxd439ITNBVtWMubsA+ufdGc+gDGiftv/4XA5Kv+DirXeOULipeHr/TwKzMym5giFNCUcat\
+5Y3xGhcN/QsRof/4X2ztugunMX9E3Ccg1V6jlIuaL1/GJQusyPJc8SYh56hp3CKdDI83HfJn7sOHPobNaipwMCXlhq29xu+B+/c0rMcspNWp8U8fBf0bdPs5jbjX7juavZOlbgPQT/eMxqK3qXtz686BubAXTexY\
+RR0zr38KpBPT0CuujNa9/rr3Puq141476bXTXtv02rbb1j14dKd/4Dc6Pf27G/Tp5Tcf/6k/+op2dE0euoqnruKxfju9oj25om0ubZ9d0npzSatzh/XKtr20vbhs71z5c919m14LR2fXWHcf8voKKdCDXPcg6V9g\
+rjvjrfmNm36jM+wdv7HnNzqmSYcg73uSpgdn3mvbXruKV+wS/Tfu4r9aCvxRKfFHpcgflTJ/VApd1b7mj1ZtbNPtwAnuPCp+El8lcXd88518EgR0O22VjrtwpZts9vpWcjyJVGLMp/8HHp1i9w==\
+""",
+"esp32": b"""
+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\
+""",
+"esp32s2": b"""
+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\
+""",
+}
+
+for key, stub in stubs.items():
+ code = eval(zlib.decompress(base64.b64decode(stub)))
+ print("Processing " + key)
+ print("Text size:", str(len(code["text"])) + " bytes")
+ print("Data size:", str(len(code["data"])) + " bytes")
+
+ print(code["text"])
+ print(base64.b64encode(code["text"]))
+ code["text"] = base64.b64encode(code["text"]).decode("utf-8")
+ code["data"] = base64.b64encode(code["data"]).decode("utf-8")
+
+ jsondata = json.dumps(code)
+
+ f = open(key + ".json", "w+")
+ f.write(jsondata)
+ f.close()
diff --git a/index.html b/index.html
index 0dfb7a3..52da8cd 100644
--- a/index.html
+++ b/index.html
@@ -57,9 +57,9 @@
-
+
diff --git a/js/script.js b/js/script.js
index 03dd4ce..20e9417 100644
--- a/js/script.js
+++ b/js/script.js
@@ -1,18 +1,12 @@
-// let the editor know that `Chart` is defined by some code
-// included in another file (in this case, `index.html`)
-// Note: the code will still work without this line, but without it you
-// will see an error in the editor
-/* global TransformStream */
-/* global TextEncoderStream */
-/* global TextDecoderStream */
-
-//'use strict';
+'use strict';
let port;
let reader;
let inputStream;
let outputStream;
+let espTool;
let isConnected = false;
+let stubLoader = null;
const baudRates = [921600, 115200, 230400, 460800];
const flashSizes = {
@@ -32,7 +26,7 @@ const ESP32S2_FLASH_WRITE_SIZE = 0x400;
const FLASH_SECTOR_SIZE = 0x1000; // Flash sector size, minimum unit of erase.
const ESP_ROM_BAUD = 115200;
-const SYNC_PACKET = toUTF8Array("\x07\x07\x12 UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU");
+const SYNC_PACKET = toByteArray("\x07\x07\x12 UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU");
const CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000;
const ESP8266 = 0x8266;
const ESP32 = 0x32;
@@ -63,12 +57,16 @@ const ESP_CHECKSUM_MAGIC = 0xEF;
const ROM_INVALID_RECV_MSG = 0x05;
+const USB_RAM_BLOCK = 0x800;
+const ESP_RAM_BLOCK = 0x1800;
+
// Timeouts
const DEFAULT_TIMEOUT = 3000;
const CHIP_ERASE_TIMEOUT = 600000; // timeout for full chip erase in ms
const MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2; // longest any command can run in ms
const SYNC_TIMEOUT = 100; // timeout for syncing with bootloader in ms
const ERASE_REGION_TIMEOUT_PER_MB = 30000; // timeout (per megabyte) for erasing a region in ms
+const MEM_END_ROM_TIMEOUT = 50;
const bufferSize = 512;
const colors = ['#00a7e9', '#f89521', '#be1e2d'];
@@ -97,15 +95,16 @@ let buttonState = 0;
let inputBuffer = [];
document.addEventListener('DOMContentLoaded', () => {
+ espTool = new EspLoader()
butConnect.addEventListener('click', () => {
- clickConnect().catch(async (e) => {
+ clickConnect();/*.catch(async (e) => {
errorMsg(e.message);
disconnect();
toggleUIConnected(false);
- });
+ });*/
});
butClear.addEventListener('click', clickClear);
- //butErase.addEventListener('click', clickErase);
+ butErase.addEventListener('click', clickErase);
autoscroll.addEventListener('click', clickAutoscroll);
baudRate.addEventListener('change', changeBaudRate);
darkMode.addEventListener('click', clickDarkMode);
@@ -139,25 +138,10 @@ async function connect() {
// - Request a port and open a connection.
port = await navigator.serial.requestPort();
- /*
- Baud Rate should start at 115200
- on ESP8266, we can't change
- On ESP32 (and ESP32-S2), we can change after the inital 115200
- */
-
logMsg("Connecting...")
// - Wait for the port to open.toggleUIConnected
await port.open({ baudRate: ESP_ROM_BAUD });
- // Turn off Serial Break signal.
- await port.setSignals({ break: false });
-
- // Turn on Data Terminal Ready (DTR) signal.
- await port.setSignals({ dataTerminalReady: true });
-
- // Turn off Request To Send (RTS) signal.
- await port.setSignals({ requestToSend: false });
-
const signals = await port.getSignals();
logMsg("Connected successfully.")
@@ -180,33 +164,33 @@ function initBaudRate() {
}
/**
- * @name toUTF8Array
- * Convert a string to a UTF8 byte array
+ * @name toByteArray
+ * Convert a string to a byte array
*/
-function toUTF8Array(str) {
- let utf8 = [];
+function toByteArray(str) {
+ let byteArray = [];
for (let i = 0; i < str.length; i++) {
let charcode = str.charCodeAt(i);
- if (charcode < 0x80) {
- utf8.push(charcode);
+ if (charcode <= 0xFF) {
+ byteArray.push(charcode);
} else if (charcode < 0x800) {
- utf8.push(0xc0 | (charcode >> 6),
- 0x80 | (charcode & 0x3f));
+ byteArray.push(0xc0 | (charcode >> 6),
+ 0x80 | (charcode & 0x3f));
} else if (charcode < 0xd800 || charcode >= 0xe000) {
- utf8.push(0xe0 | (charcode >> 12),
- 0x80 | ((charcode>>6) & 0x3f),
- 0x80 | (charcode & 0x3f));
+ byteArray.push(0xe0 | (charcode >> 12),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
} else {
i++;
charcode = 0x10000 + (((charcode & 0x3ff) << 10)
| (str.charCodeAt(i) & 0x3ff));
- utf8.push(0xf0 | (charcode >>18),
- 0x80 | ((charcode>>12) & 0x3f),
- 0x80 | ((charcode>>6) & 0x3f),
- 0x80 | (charcode & 0x3f));
+ byteArray.push(0xf0 | (charcode >>18),
+ 0x80 | ((charcode>>12) & 0x3f),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
}
}
- return utf8;
+ return byteArray;
}
/**
@@ -237,7 +221,6 @@ async function readLoop() {
reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
-
if (done) {
reader.releaseLock();
break;
@@ -261,11 +244,29 @@ function logMsg(text) {
}
function debugMsg(...args) {
- let isStrict = (function() { return !this; })();
- let prefix = "";
- if (!isStrict) {
- prefix = '
[' + debugMsg.caller.name + '] ';
+ function getStackTrace() {
+ let stack = new Error().stack;
+ //console.log(stack);
+ stack = stack.split("\n").map(v => v.trim());
+ stack.shift();
+ stack.shift();
+
+ let trace = [];
+ for (let line of stack) {
+ line = line.replace("at ", "");
+ trace.push({
+ "func": line.substr(0, line.indexOf("(") - 1),
+ "pos": line.substring(line.indexOf(".js:") + 4, line.lastIndexOf(":"))
+ });
+ }
+
+ return trace;
}
+
+ let stack = getStackTrace();
+ stack.shift();
+ let top = stack.shift();
+ let prefix = '
[' + top.func + ":" + top.pos + '] ';
for (let arg of args) {
if (typeof arg == "string") {
logMsg(prefix + arg);
@@ -354,8 +355,9 @@ async function clickConnect() {
}
await connect();
+
toggleUIConnected(true);
- try {
+ //try {
if (await espTool.sync()) {
toggleUIToolbar(true);
appDiv.classList.add("connected");
@@ -365,13 +367,14 @@ async function clickConnect() {
}
logMsg("Connected to " + await espTool.chipName());
logMsg("MAC Address: " + formatMacAddr(espTool.macAddr()));
+ stubLoader = await espTool.runStub();
}
- } catch(e) {
+ /*} catch(e) {
errorMsg(e);
await disconnect();
toggleUIConnected(false);
return;
- }
+ }*/
}
/**
@@ -412,7 +415,7 @@ async function clickDarkMode() {
async function clickErase() {
baudRate.disabled = true;
try {
- await espTool.eraseFlash();
+ await stubLoader.eraseFlash();
} catch(e) {
errorMsg(e);
} finally {
@@ -436,16 +439,16 @@ async function uploadFirmware() {
firmware.disabled = true;
let label = firmware.nextElementSibling;
let labelVal = label.innerHTML;
- try {
+ //try {
label.querySelector('span').innerHTML = "Programming...";
await espTool.flashData(event.target.result, parseInt(offset.value, 16));
- } catch(e) {
+ /*} catch(e) {
errorMsg(e);
} finally {
label.innerHTML = labelVal;
baudRate.disabled = false;
firmware.disabled = false;
- }
+ }*/
});
reader.readAsArrayBuffer(binfile);
}
@@ -468,13 +471,15 @@ function toggleUIToolbar(show) {
}
firmware.disabled = !show;
offset.disabled = !show;
- //butErase.disabled = !show;
+ butErase.disabled = !show;
}
function toggleUIConnected(connected) {
let lbl = 'Connect';
if (connected) {
lbl = 'Disconnect';
+ } else {
+ toggleUIToolbar(false);
}
butConnect.textContent = lbl;
}
@@ -507,18 +512,21 @@ function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
-let espTool = {
- _chipfamily: null,
- _efuses: new Array(4).fill(0),
- _flashsize: 4 * 1024 * 1024,
- debug: false,
+class EspLoader {
+ constructor() {
+ this._chipfamily = null;
+ this._efuses = new Array(4).fill(0);
+ this._flashsize = 4 * 1024 * 1024;
+ this.debug = true;
+ this.IS_STUB = false;
+ }
/**
* @name slipEncode
* Take an array buffer and return back a new array where
* 0xdb is replaced with 0xdb 0xdd and 0xc0 is replaced with 0xdb 0xdc
*/
- slipEncode: function(buffer) {
+ slipEncode(buffer) {
let encoded = [];
for (let byte of buffer) {
if (byte == 0xDB) {
@@ -530,13 +538,13 @@ let espTool = {
}
}
return encoded;
- },
+ };
/**
* @name macAddr
* The MAC address burned into the OTP memory of the ESP chip
*/
- macAddr: function() {
+ macAddr() {
let macAddr = new Array(6).fill(0);
let mac0 = this._efuses[0];
let mac1 = this._efuses[1];
@@ -578,13 +586,13 @@ let espTool = {
throw("Unknown chip family")
}
return macAddr;
- },
+ };
/**
* @name _readEfuses
* Read the OTP data for this chip and store into this.efuses array
*/
- _readEfuses: async function() {
+ async _readEfuses() {
let baseAddr
if (this._chipfamily == ESP8266) {
baseAddr = 0x3FF00050;
@@ -598,26 +606,26 @@ let espTool = {
for (let i = 0; i < 4; i++) {
this._efuses[i] = await this.readRegister(baseAddr + 4 * i);
}
- },
+ };
/**
* @name readRegister
* Read a register within the ESP chip RAM, returns a 4-element list
*/
- readRegister: async function(reg) {
+ async readRegister(reg) {
if (this.debug) {
debugMsg("Reading Register", reg);
}
let packet = this.pack("I", reg);
let register = (await this.checkCommand(ESP_READ_REG, packet))[0];
return this.unpack("I", register)[0];
- },
+ };
/**
* @name chipType
* ESP32 or ESP8266 based on which chip type we're talking to
*/
- chipType: async function() {
+ async chipType() {
if (this._chipfamily === null) {
let datareg = await this.readRegister(0x60000078);
if (datareg == ESP32_DATAREGVALUE) {
@@ -631,14 +639,14 @@ let espTool = {
}
}
return this._chipfamily;
- },
+ };
/**
* @name chipType
* The specific name of the chip, e.g. ESP8266EX, to the best
* of our ability to determine without a stub bootloader.
*/
- chipName: async function() {
+ async chipName() {
await this.chipType();
await this._readEfuses();
@@ -655,7 +663,7 @@ let espTool = {
return "ESP8266EX";
}
return null;
- },
+ };
/**
* @name checkCommand
@@ -663,27 +671,30 @@ let espTool = {
* return a tuple with the value and data.
* See the ESP Serial Protocol for more details on what value/data are
*/
- checkCommand: async function(opcode, buffer, checksum=0, timeout=DEFAULT_TIMEOUT) {
+ async checkCommand(opcode, buffer, checksum=0, timeout=DEFAULT_TIMEOUT) {
timeout = Math.min(timeout, MAX_TIMEOUT);
- await this.sendCommand(opcode, buffer);
+ debugMsg("Pre-encoded data", buffer);
+ await this.sendCommand(opcode, buffer, checksum);
let [value, data] = await this.getResponse(opcode, timeout);
- let status_len;
+ let statusLen;
if (data !== null) {
- if (this._chipfamily == ESP8266) {
- status_len = 2;
- } else if (this._chipfamily == ESP32) {
- status_len = 4;
+ if (this.IS_STUB) {
+ statusLen = 2;
+ } else if (this._chipfamily == ESP8266) {
+ statusLen = 2;
+ } else if ([ESP32, ESP32S2].includes(this._chipfamily)) {
+ statusLen = 4;
} else {
if ([2, 4].includes(data.length)) {
- status_len = data.length;
+ statusLen = data.length;
}
}
}
- if (data === null || data.length < status_len) {
+ if (data === null || data.length < statusLen) {
throw("Didn't get enough status bytes");
}
- let status = data.slice(-status_len, data.length);
- data = data.slice(0, -status_len);
+ let status = data.slice(-statusLen, data.length);
+ data = data.slice(0, -statusLen);
if (this.debug) {
debugMsg("status", status);
debugMsg("value", value);
@@ -697,32 +708,28 @@ let espTool = {
}
}
return [value, data];
- },
+ };
/**
* @name timeoutPerMb
* Scales timeouts which are size-specific
*/
- timeoutPerMb: function(secondsPerMb, sizeBytes) {
+ timeoutPerMb(secondsPerMb, sizeBytes) {
let result = Math.floor(secondsPerMb * (sizeBytes / 0x1e6));
if (result < DEFAULT_TIMEOUT) {
return DEFAULT_TIMEOUT;
}
return result;
- },
+ };
/**
* @name sendCommand
* Send a slip-encoded, checksummed command over the UART,
* does not check response
*/
- sendCommand: async function(opcode, buffer) {
+ async sendCommand(opcode, buffer, checksum=0) {
//debugMsg("Running Send Command");
inputBuffer = []; // Reset input buffer
- let checksum = 0;
- if (opcode == 0x03) {
- checksum = this.checksum(buffer.slice(16));
- }
let packet = [0xC0, 0x00]; // direction
packet.push(opcode);
packet = packet.concat(this.pack("H", buffer.length));
@@ -733,7 +740,7 @@ let espTool = {
debugMsg("Writing " + packet.length + " byte" + (packet.length == 1 ? "" : "s") + ":", packet);
}
await writeToStream(packet);
- },
+ };
/**
* @name getResponse
@@ -741,18 +748,17 @@ let espTool = {
* out the value/data and returns as a tuple of (value, data) where
* each is a list of bytes
*/
- getResponse: async function (opcode, timeout=DEFAULT_TIMEOUT) {
+ async getResponse(opcode, timeout=DEFAULT_TIMEOUT) {
let reply = [];
- let packet_length = 0;
- let escaped_byte = false;
- let timedOut = false;
+ let packetLength = 0;
+ let escapedByte = false;
let stamp = Date.now();
while (Date.now() - stamp < timeout) {
if (inputBuffer.length > 0) {
let c = inputBuffer.shift();
if (c == 0xDB) {
- escaped_byte = true;
- } else if (escaped_byte) {
+ escapedByte = true;
+ } else if (escapedByte) {
if (c == 0xDD) {
reply.push(0xDC);
} else if (c == 0xDC) {
@@ -760,7 +766,7 @@ let espTool = {
} else {
reply = reply.concat([0xDB, c]);
}
- escaped_byte = false;
+ escapedByte = false;
} else {
reply.push(c);
}
@@ -779,15 +785,15 @@ let espTool = {
}
if (reply.length > 4) {
// get the length
- packet_length = reply[3] + (reply[4] << 8);
+ packetLength = reply[3] + (reply[4] << 8);
}
- if (reply.length == packet_length + 10) {
+ if (reply.length == packetLength + 10) {
break;
}
}
// Check to see if we have a complete packet. If not, we timed out.
- if (reply.length != packet_length + 10) {
+ if (reply.length != packetLength + 10) {
logMsg("Timed out after " + timeout + " milliseconds");
return [null, null];
}
@@ -800,20 +806,76 @@ let espTool = {
debugMsg("value:", value, "data:", data);
}
return [value, data];
- },
+ };
+
+/**
+ * @name read
+ * Read response data and decodes the slip packet.
+ * Keeps reading until we hit the timeout or get
+ * a packet closing byte
+ */
+ async readBuffer(timeout=DEFAULT_TIMEOUT) {
+ let reply = [];
+ let packetLength = 0;
+ let escapedByte = false;
+ let stamp = Date.now();
+ while (Date.now() - stamp < timeout) {
+ if (inputBuffer.length > 0) {
+ let c = inputBuffer.shift();
+ if (c == 0xDB) {
+ escapedByte = true;
+ } else if (escapedByte) {
+ if (c == 0xDD) {
+ reply.push(0xDC);
+ } else if (c == 0xDC) {
+ reply.push(0xC0);
+ } else {
+ reply = reply.concat([0xDB, c]);
+ }
+ escapedByte = false;
+ } else {
+ reply.push(c);
+ }
+ } else {
+ await sleep(10);
+ }
+ if (reply.length > 0 && reply[0] != 0xC0) {
+ // packets must start with 0xC0
+ reply.shift();
+ }
+ if (reply.length > 1 && reply[reply.length - 1] == 0xC0) {
+ break;
+ }
+ }
+
+ // Check to see if we have a complete packet. If not, we timed out.
+ if (reply.length < 2) {
+ logMsg("Timed out after " + timeout + " milliseconds");
+ return null;
+ }
+ if (this.debug) {
+ debugMsg("Reading " + reply.length + " byte" + (reply.length == 1 ? "" : "s") + ":", reply);
+ }
+ let data = reply.slice(1, -1);
+ if (this.debug) {
+ debugMsg("data:", data);
+ }
+ return data;
+ };
+
/**
* @name checksum
* Calculate checksum of a blob, as it is defined by the ROM
*/
- checksum: function(data, state=ESP_CHECKSUM_MAGIC) {
+ checksum(data, state=ESP_CHECKSUM_MAGIC) {
for (let b of data) {
state ^= b;
}
return state;
- },
+ };
- setBaudrate: async function (baud) {
+ async setBaudrate(baud) {
if (this._chipfamily == ESP8266) {
logMsg("Baud rate can only change on ESP32 and ESP32-S2");
}
@@ -823,9 +885,9 @@ let espTool = {
await sleep(50);
await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
logMsg("Changed baud rate to " + port.baudRate);
- },
+ };
- pack: function(...args) {
+ pack(...args) {
let format = args[0];
let pointer = 0;
let data = args.slice(1);
@@ -865,9 +927,9 @@ let espTool = {
}
return bytes;
- },
+ };
- unpack: function(format, bytes) {
+ unpack(format, bytes) {
let pointer = 0;
let data = [];
for (let c of format) {
@@ -892,14 +954,14 @@ let espTool = {
}
}
return data;
- },
+ };
/**
* @name sync
* Put into ROM bootload mode & attempt to synchronize with the
* ESP ROM bootloader, we will retry a few times
*/
- sync: async function() {
+ async sync() {
for (let i = 0; i < 5; i++) {
let response = await this._sync();
if (response) {
@@ -910,14 +972,14 @@ let espTool = {
}
throw("Couldn't sync to ESP. Try resetting.");
- },
+ };
/**
* @name _sync
* Perform a soft-sync using AT sync packets, does not perform
* any hardware resetting
*/
- _sync: async function() {
+ async _sync() {
await this.sendCommand(ESP_SYNC, SYNC_PACKET);
for (let i = 0; i < 8; i++) {
let [reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
@@ -929,18 +991,18 @@ let espTool = {
}
}
return false;
- },
+ };
/**
* @name getFlashWriteSize
* Get the Flash write size based on the chip
*/
- getFlashWriteSize: function() {
+ getFlashWriteSize() {
if (this._chipfamily == ESP32S2) {
return ESP32S2_FLASH_WRITE_SIZE;
}
return FLASH_WRITE_SIZE;
- },
+ };
/**
* @name flashData
@@ -949,7 +1011,7 @@ let espTool = {
* verify memory. ESP8266 does not have checksum memory verification in
* ROM
*/
- flashData: async function(binaryData, offset=0) {
+ async flashData(binaryData, offset=0) {
let filesize = binaryData.byteLength;
logMsg("\nWriting data with filesize:" + filesize);
@@ -980,27 +1042,27 @@ let espTool = {
}
logMsg("Took " + (Date.now() - stamp) + "ms to write " + filesize + " bytes");
logMsg("To run the new firmware, please reset your device.")
- },
+ };
/**
* @name flashBlock
* Send one block of data to program into SPI Flash memory
*/
- flashBlock: async function(data, seq, timeout=100) {
+ async flashBlock(data, seq, timeout=100) {
await this.checkCommand(
ESP_FLASH_DATA,
this.pack("
start) {
+ throw("Software loader is resident at " + toHex(start, 8) + "-" + toHex(end, 8) + ". " +
+ "Can't load binary at overlapping address range " + toHex(load_start, 8) + "-" + toHex(load_end, 8) + ". " +
+ "Try changing the binary loading address.");
+ }
+ }
+ }
+
+ return this.checkCommand(ESP_MEM_BEGIN, this.pack(' length) {
+ toOffs = length;
+ }
+ await this.memBlock(stub[field].slice(fromOffs, toOffs), seq);
+ }
+ }
+ }
+ logMsg("Running stub...")
+ await this.memFinish(stub['entry']);
+
+ let p = await this.readBuffer(100);
+ p = String.fromCharCode(...p);
+
+ if (p != 'OHAI') {
+ throw "Failed to start stub. Unexpected response: " + p;
+ }
+ logMsg("Stub running...");
+ return new EspStubLoader();
+ }
+}
+
+class EspStubLoader extends EspLoader {
+ /*
+ The Stubloader has commands that run on the uploaded Stub Code in RAM
+ rather than built in commands.
+ */
+ constructor() {
+ super();
+ this.IS_STUB = true;
+ }
+ /**
+ * @name getEraseSize
+ * depending on flash chip model the erase may take this long (maybe longer!)
+ */
+ async eraseFlash() {
+ await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
+ };
}
diff --git a/stubs/.DS_Store b/stubs/.DS_Store
new file mode 100644
index 0000000..5450231
Binary files /dev/null and b/stubs/.DS_Store differ
diff --git a/stubs/esp32.json b/stubs/esp32.json
new file mode 100644
index 0000000..102daef
--- /dev/null
+++ b/stubs/esp32.json
@@ -0,0 +1 @@
+{"text": "CAD0PxwA9D8AAPQ/pOv9PxAA9D82QQAh+v/AIAA4AkH5/8AgACgEICB0nOIGBQAAAEH1/4H2/8AgAKgEiAigoHTgCAALImYC54b0/yHx/8AgADkCHfAAAPgg9D/4MPQ/NkEAkf3/wCAAiAmAgCRWSP+R+v/AIACICYCAJFZI/x3wAAAAECD0PwAg9D8AAAAINkEA5fz/Ifv/DAjAIACJApH7/4H5/8AgAJJoAMAgAJgIVnn/wCAAiAJ88oAiMCAgBB3wAAAAAEA2QQBl/P8Wmv+B7f+R/P/AIACZCMAgAJgIVnn/HfAAAAAAAAEAAIAAmMD9P////wAEIPQ/NkEAIfz/MiIEFkMFZfj/FuoEpfv/OEIM+AwUUfT/N6gLOCKAMxDMM1Hy/xwEiCJAOBEl8/+B8P+AgxAx8P/AIACJAzHS/8AgAFJjAMAgAFgDVnX/OEJAM8A5QjgiSkNJIh3wAJDA/T8IQP0/gIAAAISAAABAQAAASID9P5TA/T82QQCx+P8goHRlrwCW6gWB9v+R9v+goHSQmIDAIACyKQCR8/+QiIDAIACSGACQkPQbycDA9MAgAMJYAJqbwCAAokkAwCAAkhgAger/kJD0gID0h5lGgeT/keX/oej/mpjAIADICbHk/4ecGUYCAHzohxrhRgkAAADAIACJCsAgALkJRgIAwCAAuQrAIACJCZHY/5qIDAnAIACSWAAd8AAAUC0GQDZBAEGz/1g0UDNjFuMDWBRaU1BcQYYAACXs/4hEphgEiCSHpfKl5P8Wmv+oFDDDICCyIIHy/+AIAIw6IqDEKVQoFDoiKRQoNDAywDk0HfAACCD0PwAAQABw4vo/SCQGQPAiBkA2YQCl3f+tAYH8/+AIAD0KDBLs6ogBkqIAkIgQiQFl4v+R8v+h8//AIACICaCIIMAgAIJpALIhAKHv/4Hw/+AIAKAjgx3wAAD/DwAANkEAgYf/kqABkkgAMJxBkmgCkfr/MmgBKTgwMLSaIiozMDxBDAIpWDlIpfj/LQqMGiKgxR3wAAAskgBANkEAgqDArQKHkg6ioNuB+//gCACioNyGAwCCoNuHkgiB9//gCACioN2B9P/gCAAd8AAAADZBADoyBgIAAKICABsi5fv/N5L0HfAAAAAQAABYEAAAfNoFQNguBkCc2gVAHNsFQDYhIaLREIH6/+AIAIYJAABR9v+9AVBDY80ErQKB9v/gCAD8Ks0EvQGi0RCB8//gCABKIkAzwFZj/aHs/7LREBqqge7/4AgAoen/HAsaqiX4/y0DBgEAAAAioGMd8AAAADZBAKKgwIHM/+AIAB3wAABsEAAAaBAAAHAQAAB0EAAAeBAAAPxnAEDQkgBACGgAQDZBIWH5/4H5/xpmSQYaiGLREAwELApZCEJmGoH2/+AIAFHx/4HN/xpVWAVXuAIGNwCtBoHL/+AIAIHt/3Hp/xqIelFZCEYlAIHo/0BzwBqIiAi9AXB4Y80HIKIggcL/4AgAjLpx4P8MBVJmFnpxhgwA5fX/cLcgrQFl7P8l9f/NB70BYKYggbj/4AgAeiJ6RDe00IHW/1B0wBqIiAiHN6cG8P8ADAqiRmyB0f8aiKIoAIHR/+AIAFbq/rGo/6IGbBq7pXsA9+oM9kUJWreiSwAbVYbz/7Kv/reayGZFCFImGje1Ale0qKGd/2C2IBCqgIGf/+AIAKXt/6GY/xwLGqrl4//l7P8sCoG9/+AIAB3wAMD8P09IQUmo6/0/fOELQBTgC0AMAPQ/OED0P///AAAAAAEAjIAAABBAAAAAQAAAAMD8PwTA/D8QJwAAFAD0P/D//wCo6/0/CMD8P7DA/T98aABA7GcAQFiGAEBsKgZAODIGQBQsBkDMLAZATCwGQDSFAEDMkABAeC4GQDDvBUBYkgBATIIAQDbBACHe/wwKImEIQqAAge7/4AgAIdn/Mdr/BgEAQmIASyI3Mvcl4f8MS6LBIKXX/2Xg/zHm/iHm/kHS/yojwCAAOQKx0f8hi/4MDAxaSQKB3//gCABBzf9SoQHAIAAoBCwKUCIgwCAAKQSBfv/gCACB2P/gCAAhxv/AIAAoAsy6HMRAIhAiwvgMFCCkgwwLgdH/4AgA8b//0Ur/wb//saz+4qEADAqBzP/gCAAhvP8MBSozIan+YtIrwCAAKAMWcv/AIAAoAwwUwCAAWQNCQRBCAgEMJ0JBEXJRCVlRJpQHHDd3FB4GCABCAgNyAgKARBFwRCBmRBFIIsAgAEgESVFGAQAAHCRCUQnl0v8Mi6LBEGXJ/0ICA3ICAoBEEXBEIHGg/3Bw9Ee3EqKgwGXE/6Kg7iXE/yXQ/0bf/wByAgEM2ZeXAoafAHc5TmZnAgbJAPZ3IGY3AsZxAPZHCGYnAkZXAAYmAGZHAkaFAGZXAoakAEYiAAyZl5cCxpcAdzkIZncCRqYARh0AZpcChpkADLmXlwJGggAGGQAcOZeXAgZCAHc5Kma3AsZPABwJdzkMDPntBZeXAoY2AMYQABwZl5cCBlcAHCRHlwIGbQCGCwCSoNKXlwLGMgB3ORCSoNCXFySSoNGXFzHGBAAAAJKg05eXAoY6AZKg1JeXAoZIAO0FcqD/RqMADBdWZCiBdP/gCACgdIOGngAAACaEBAwXBpwAQiICciIDcJQgkJC0Vrn+pav/cESAnBoG+P8AoKxBgWj/4AgAVjr9ctfwcKTAzCeGcQAAoID0Vhj+RgQAoKD1gWH/4AgAVir7gUv/gHfAgUr/cKTAdzjkxgMAAKCsQYFY/+AIAFY6+XLX8HCkwFan/kZhAHKgwCaEAoZ9AO0FRlMAAAAmtPUGVAByoAEmtAKGdwCyIgOiIgLlsf8GCQAAcqABJrQCBnIAkTb/QiIEUOUgcqDCR7kCBm4AuFKoIgwXZaX/oHWDxmkADBlmtCxIQqEs/+0FcqDCR7oCBmUAeDK4UqgicHSCmeHlov9BEv6Y4VlkQtQreSSglYN9CQZcAJEN/u0FogkAcqDGFkoWeFmYIkLE8ECZwKKgwJB6kwwKkqDvhgIAAKqysgsYG6qwmTBHKvKiAgVCAgSAqhFAqiBCAgbtBQBEEaCkIEICB4BEAaBEIECZwEKgwZB0k4ZEAEH1/e0FkgQAcqDGFkkQmDRyoMhWyQ+SRAB4VAY9AAAcie0FDBeXFALGOQDoYvhy2FLIQrgyqCKBCP/gCADtCqB1g0YzAAwXJkQCxjAAqCK9BYEA/+AIAIYPAADtBXKgwCa0AgYrAEgieDLAIAB5BAwHhicAZkQCRqj/7QVyoMAGJAAADBcmtAJGIQBB5/6YUngimQRB5f55BH0FhhwAseL+DBfYC0LE8J0FQJeT0HWTcJkQ7QVyoMZWeQWB3P5yoMnICEc8TECgFHKgwFY6BH0KDB+GAgAAepKYaUt3mQqdD3qtcOzARzftFvniqQvpCAaK/wAMF2aEF0HM/ngEjBdyoMhZBAwaQcj+cKWDWQR9Cu0FcKB04mENpY3/4iEN4KB0JY3/JZn/VsfAQgIBcqAPdxRARzcUZkQCRnkAZmQCxn8AJjQChvv+hh8AHCd3lAKGcwBHNwscF3eUAgY6AEb1/gByoNJ3FE9yoNR3FHNG8f4AAAC4MqGu/ngiucGBuv7gCAAhq/6RrP7AIAAoArjBIEQ1wCIRkCIQICQgsLKCrQVwu8KBsf7gCACio+iBrv7gCAAG4P4AANIiBcIiBLIiA6giJZL/Rtv+ALICA0ICAoC7EUC7ILLL8KLCGKVy/wbV/kICA3ICAoBEEXBEIHF6/ULE8Jg3kERjFqSzmBealJCcQQYCAJJhDqVd/5IhDqInBKYaBKgnp6nrpVX/Fpr/oicBQMQgssIYgZH+4AgAFkoAIqDEKVcoF0oiKRcoN0BCwEk3xrv+cgIDkgICgHcRkHcgQsIYcsfwDBwGIACRd/4hev3iKQByYQfgIsAiYQYoJgwaJ7cBDDqZ4anB6dElVv+owSFu/qkB6NGhbf69BMLBHPLBGN0CgXb+4AgAzQq4JqhxmOGgu8C5JqB3wLgJqkSoYaq7C6ygrCC5CaCvBSC7wMya0tuADB7QroMW6gCtApnhycHlYv+Y4cjBKQmBPf0oOIynwJ8xwJnA1ikAVrL21qwAgTj9QqDHSVhGAACMPJwCxov+FsKiQTP9IqDIKVRGiP4AgTD9IqDJKVhGhf4AKCJW8qCtBYFT/uAIAKE//oFN/uAIAIFQ/uAIAEZ9/gAoMhbynq0FgUv+4AgAoqPogUX+4AgA4AIABnb+HfAAADZBAJ0CgqDAKAOHmQ/MMgwShgcADAIpA3zihg4AJhIHJiIWhgMAAACCoNuAKSOHmSYMIikDfPJGBwAioNwnmQgMEikDLQiGAwCCoN188oeZBgwSKQMioNsd8AAA", "text_start": 1074520064, "entry": 1074521496, "data": "CMD8Pw==", "data_start": 1073605544}
diff --git a/stubs/esp32s2.json b/stubs/esp32s2.json
new file mode 100644
index 0000000..523455e
--- /dev/null
+++ b/stubs/esp32s2.json
@@ -0,0 +1 @@
+{"text": "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", "text_start": 1073905664, "entry": 1073907516, "data": "CAD9Pw==", "data_start": 1073622004}
diff --git a/stubs/esp8266.json b/stubs/esp8266.json
new file mode 100644
index 0000000..15e6924
--- /dev/null
+++ b/stubs/esp8266.json
@@ -0,0 +1 @@
+{"text": "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", "text_start": 1074847744, "entry": 1074847748, "data": "CIH+PwUFBAACAwcAAwMLAFHnEECH5xBAtecQQFToEEAF9xBAuugQQBDpEEBc6RBABfcQQCLqEECf6hBAYOsQQAX3EEAF9xBA+OsQQAX3EEDX7hBAn+8QQNjvEEAF9xBABfcQQHXwEEAF9xBAW/EQQAHyEEBA8xBA//MQQND0EEAF9xBABfcQQAX3EEAF9xBA/vUQQAX3EED09hBAL+0QQCfoEEBC9RBAS+oQQJjpEEAF9xBAiPYQQM/2EEAF9xBABfcQQAX3EEAF9xBABfcQQAX3EEAF9xBABfcQQMDpEED/6RBAWvUQQAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAkAAAANAAAAEQAAABkAAAAhAAAAMQAAAEEAAABhAAAAgQAAAMEAAAABAQAAgQEAAAECAAABAwAAAQQAAAEGAAABCAAAAQwAAAEQAAABGAAAASAAAAEwAAABQAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAAAAAAAAAAAAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAANAAAADwAAABEAAAATAAAAFwAAABsAAAAfAAAAIwAAACsAAAAzAAAAOwAAAEMAAABTAAAAYwAAAHMAAACDAAAAowAAAMMAAADjAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADAAAAAwAAAAMAAAAEAAAABAAAAAQAAAAEAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAABAREgAIBwkGCgULBAwDDQIOAQ8AAQEAAAEAAAAEAAAA", "data_start": 1073720488}