diff --git a/README.md b/README.md index 4db8cd1..209a905 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,41 @@ -# Pixelart +#

DerEffi's Pixelart Display

-## Usage -Nobody reads the instructions... -Just plug it in :P + -## Installation -TBD +## About -## Notes -- image upload in 64x64 (browser downscaling not that great) -- image upload in rgb colorspace (browser color conversion not that great) -- no alpha channel (cant be displayed by led panel and is ignored) +This project is an ESP32 Display Matrix for displaying images, time and social stats on a 64x64 LED Matrix Panel (HUB75E). An overview about the functionalities can be found at the Project Site [pixelart.dereffi.de](http://pixelart.dereffi.de). + +## Disclaimer + +This project is not a product, but rather a diy project. I won't sell it. I probably won't do another one at all. There are some minor issues/inconviniences that I plan to address in the future, but I can not make any promises on if or when that could happen. + +That being said, in general I'm happy with the current state (althought it's not perfect) and the core functionality works as expected. You can use my code to rebuild it yourself or use parts of the code for your own project. If there are any issues, I'm pleased to help. If you have any solutions I'm even more pleased if you contribute. + +## Usage/Deployment + +The project consist of three parts: +- the controller for the display: [/pixelart-controller](./pixelart-controller/) +- the webinterface for remote control of the display: [/pixelart-interface](./pixelart-interface/) +- the socials api for retrieving social data from different platforms: [/pixelart-api](./pixelart-api/) + +The documentations for these projects can be found in the different subdirectories as well as additional files for api, schematics and my dev board pinout under `/docs`. +An already compiled version of the firmware and the webinterface for the device can be found on the project site for download if you plan to use it as is. +Please be aware, that I won't be giving out any api-keys for my hosted social-api service (more on that under [/pixelart-api](./pixelart-api/README.md)), so you will have to host this on your own. + +## Credits + +Among a few other libraries I use in this project, thanks especially to: +- [mrfaptastic's ESP32/HUB75 Library](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA) used to output on the LED matrix at all +- [me-no-dev's Async Webserver library](https://github.com/me-no-dev/ESPAsyncWebServer) for making the webinterface possible +- [khoih-prog's Async HTTP Request Library](https://github.com/khoih-prog/AsyncHTTPSRequest_Generic) for retrieving social data on the controller +- [pgrimaud's instagram fetcher](https://github.com/pgrimaud/instagram-user-feed) for the retrieval of instagram data. Even if I don't use this library directly, I used it as a template for the authentication agains instagram apis ## Project Site -[pixelart.dereffi.de](pixelart.dereffi.de) + +[pixelart.dereffi.de](http://pixelart.dereffi.de) ## Author + [DerEffi](https://dereffi.de) [info@dereffi.de](mailto:info@dereffi.de) \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..7f68f57 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +Issues.md \ No newline at end of file diff --git a/docs/API/Pixelart.postman_collection.json b/docs/API/Pixelart.postman_collection.json new file mode 100644 index 0000000..a7d856e --- /dev/null +++ b/docs/API/Pixelart.postman_collection.json @@ -0,0 +1,501 @@ +{ + "info": { + "_postman_id": "586f1ead-fa50-4d92-a90b-2ece26718bb7", + "name": "Pixelart", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "7829197" + }, + "item": [ + { + "name": "ServerAPI", + "item": [ + { + "name": "API->SocialRequests", + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{socialsApiKey}}", + "type": "string" + }, + { + "key": "key", + "value": "Authorization", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\r\n\t{\r\n\t\t\"t\": \"t\",\r\n\t\t\"c\": \"dereffi\",\r\n\t\t\"d\": \"@DerEffi\"\r\n\t},\r\n\t{\r\n\t\t\"t\": \"y\",\r\n\t\t\"c\": \"jadereffi\",\r\n\t\t\"d\": \"@JaDerEffi\"\r\n\t},\r\n\t{\r\n\t\t\"t\": \"i\",\r\n\t\t\"c\": \"jadereffi\",\r\n\t\t\"d\": \"@JaDerEffi\"\r\n\t}\r\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/socials", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "socials" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Display", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/display", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "display" + ] + } + }, + "response": [] + }, + { + "name": "Display", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"displayMode\": 0,\r\n\t\"brightness\": 40,\r\n\t\"diashow\": false,\r\n\t\"animation\": true,\r\n\t\"animationTime\": 50,\r\n\t\"diashowTime\": 2000,\r\n\t\"diashowModes\": false\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{deviceIp}}/api/display", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "display" + ] + } + }, + "response": [] + }, + { + "name": "Images", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{deviceIp}}/api/images", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "images" + ] + } + }, + "response": [] + }, + { + "name": "Images", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"displayImage\": 0,\r\n\t\"imageOperations\": [\r\n\t\t{\r\n\t\t\t\"src\": \"001 - dereffi\",\r\n\t\t\t\"dst\": \"002 - dereffi\"\r\n\t\t}\r\n\t]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{deviceIp}}/api/images", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "images" + ] + } + }, + "response": [] + }, + { + "name": "Socials", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/socials", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "socials" + ] + } + }, + "response": [] + }, + { + "name": "Socials", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"request\": \"[{\\\"t\\\":\\\"t\\\",\\\"c\\\":\\\"dereffi\\\",\\\"d\\\":\\\"@DerEffi\\\"},{\\\"t\\\":\\\"i\\\",\\\"c\\\":\\\"jadereffi\\\",\\\"d\\\":\\\"@JaDerEffi\\\"},{\\\"t\\\":\\\"y\\\",\\\"c\\\":\\\"jadereffi\\\",\\\"d\\\":\\\"@JaDerEffi\\\"}]\",\r\n\t\"server\": \"https://api.pixelart.dereffi.de\",\r\n\t\"apiKey\": \"{{socialsAPIKey}}\",\r\n\t\"displayChannel\": 0\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{deviceIp}}/api/socials", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "socials" + ] + } + }, + "response": [] + }, + { + "name": "Time", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{deviceIp}}/api/time", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "time" + ] + } + }, + "response": [] + }, + { + "name": "Time", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"time\": 0,\r\n\t\"displayMode\": 0,\r\n\t\"seconds\": true,\r\n\t\"year\": true,\r\n\t\"blink\": false,\r\n\t\"format24\": true,\r\n\t\"updateTime\": true,\r\n\t\"ntpServer\": \"pool.ntp.org\",\r\n\t\"timezone\": \"CET-1CEST,M3.5.0,M10.5.0/3\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{deviceIp}}/api/time", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "time" + ] + } + }, + "response": [] + }, + { + "name": "Wifi", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/wifi", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "wifi" + ] + } + }, + "response": [] + }, + { + "name": "Wifi Scan", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/wifi/available", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "wifi", + "available" + ] + } + }, + "response": [] + }, + { + "name": "Wifi", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"ap\": true,\r\n\t\"apSSID\": \"DerEffi's Pixelart\",\r\n\t\"apPassword\": \"00000000\",\r\n\t\"connect\": false,\r\n\t\"ssid\": \"\",\r\n\t\"password\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{deviceIp}}/api/wifi", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "wifi" + ] + } + }, + "response": [] + }, + { + "name": "File", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "type", + "value": "images", + "type": "text" + }, + { + "key": "001 - dereffi/image.pxart", + "type": "file", + "src": [] + } + ] + }, + "url": { + "raw": "http://{{deviceIp}}/api/file", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "file" + ] + } + }, + "response": [] + }, + { + "name": "API-Key", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {\r", + "\tconst responseJson = pm.response.json();\r", + "\tvar apiKey = responseJson.apiKey;\r", + "\tif(apiKey)\r", + "\t\tpm.environment.set(\"apiKey\", apiKey);\r", + "} catch(e){}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/apiKey", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "apiKey" + ] + } + }, + "response": [] + }, + { + "name": "API-Key", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/apiKey", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "apiKey" + ] + } + }, + "response": [] + }, + { + "name": "Reset", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/reset", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "reset" + ] + } + }, + "response": [] + }, + { + "name": "Refresh", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "http://{{deviceIp}}/api/refresh", + "protocol": "http", + "host": [ + "{{deviceIp}}" + ], + "path": [ + "api", + "refresh" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "in", + "value": "header", + "type": "string" + }, + { + "key": "value", + "value": "{{apiKey}}", + "type": "string" + }, + { + "key": "key", + "value": "apiKey", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/docs/API/openapi.yml b/docs/API/openapi.yml new file mode 100644 index 0000000..4605e25 --- /dev/null +++ b/docs/API/openapi.yml @@ -0,0 +1,547 @@ +openapi: 3.0.0 +info: + title: DerEffi's Pixelart + version: 1.0.0 +servers: + - url: https://{{socialapiurl}} + - url: http://{{deviceip}} +components: + securitySchemes: + DeviceAPIKeyAuth: + type: apiKey + in: header + name: apiKey + SocialAPIKeyAuth: + type: apiKey + in: header + name: Authorization +security: + - DeviceAPIKeyAuth: [] +tags: + - name: DeviceAPI + - name: SocialAPI +paths: + /api/apiKey: + get: + tags: + - DeviceAPI + summary: Get current API-Key + security: [] + responses: + '204': + description: Requesting Menu button press on the device + content: + application/json: {} + '200': + description: Successful response if the Menu button was pressed in the last 10 seconds + content: + application/json: + schema: + type: object + properties: + apiKey: + type: string + example: supersecretapikey + delete: + tags: + - DeviceAPI + summary: Log out on all devices + responses: + '200': + description: Successful response + content: + application/json: {} + /api/display: + get: + tags: + - DeviceAPI + summary: Get current display configuration + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + displayMode: + type: number + example: 0 + brightness: + type: number + example: 40 + version: + type: string + example: '0.0.1' + freeMemory: + type: number + example: 95792 + freeSPIMemory: + type: number + example: 0 + refreshRate: + type: number + example: 155 + diashow: + type: boolean + example: false + animation: + type: boolean + example: true + animationTime: + type: number + example: 50 + diashowTime: + type: number + example: 2000 + diashowModes: + type: boolean + example: false + post: + tags: + - DeviceAPI + summary: Update basic display configuration + requestBody: + content: + application/json: + schema: + type: object + properties: + displayMode: + type: number + example: 0 + brightness: + type: number + example: 40 + diashow: + type: boolean + example: false + animation: + type: boolean + example: true + animationTime: + type: number + example: 50 + diashowTime: + type: number + example: 2000 + diashowModes: + type: boolean + example: false + responses: + '200': + description: Successful response + content: + application/json: {} + /api/images: + get: + tags: + - DeviceAPI + summary: List available images + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + displayImage: + type: number + example: 0 + imageNumber: + type: number + example: 1 + imagePrefixMax: + type: number + example: 1 + imageLoaded: + type: boolean + example: true + imageOperations: + type: array + items: + type: object + properties: + prefix: + type: number + example: 1 + folder: + type: string + example: 001 - dereffi + animated: + type: boolean + example: false + post: + tags: + - DeviceAPI + summary: Move/Delete images on device + description: Image Operations renames/moves folders within the images folder on your sd card. Sending empty destination for any operation will delete the folder. + requestBody: + content: + application/json: + schema: + type: object + properties: + displayImage: + type: number + example: 0 + imageOperations: + type: array + items: + type: object + properties: + src: + type: string + example: 001 - dereffi + dst: + type: string + example: 002 - dereffi + responses: + '202': + description: Successful response + content: + application/json: {} + /api/socials: + get: + tags: + - DeviceAPI + summary: Socials + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + request: + type: string + example: >- + [{"t":"t","c":"dereffi","d":"@DerEffi"},{"t":"i","c":"jadereffi","d":"@JaDerEffi"},{"t":"y","c":"jadereffi","d":"@JaDerEffi"}] + server: + type: string + example: 'https://{{socialapiurl}}' + apiKey: + type: string + example: '{{socialAPIKey}}' + displayChannel: + type: number + example: 0 + channelNumber: + type: number + example: 3 + post: + tags: + - DeviceAPI + summary: Socials + requestBody: + content: + application/json: + schema: + type: object + properties: + request: + type: string + example: >- + [{"t":"t","c":"dereffi","d":"@DerEffi"},{"t":"i","c":"jadereffi","d":"@JaDerEffi"},{"t":"y","c":"jadereffi","d":"@JaDerEffi"}] + server: + type: string + example: 'https://{{socialapiurl}}' + apiKey: + type: string + example: '{{socialAPIKey}}' + displayChannel: + type: number + example: 0 + responses: + '200': + description: Successful response + content: + application/json: {} + /api/time: + get: + tags: + - DeviceAPI + summary: Time + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + online: + type: number + example: 3859172 + time: + type: number + example: 1682032666 + displayMode: + type: number + example: 0 + seconds: + type: boolean + example: true + year: + type: boolean + example: true + blink: + type: boolean + example: false + format24: + type: boolean + example: true + externalRTCConnected: + type: boolean + example: true + updateTime: + type: boolean + example: true + ntpServer: + type: string + example: pool.ntp.org + timezone: + type: string + example: CET-1CEST,M3.5.0,M10.5.0/3 + post: + tags: + - DeviceAPI + summary: Time + requestBody: + content: + application/json: + schema: + type: object + properties: + time: + type: number + example: 1682032666 + displayMode: + type: number + example: 0 + seconds: + type: boolean + example: true + year: + type: boolean + example: true + blink: + type: boolean + example: false + format24: + type: boolean + example: true + updateTime: + type: boolean + example: true + ntpServer: + type: string + example: pool.ntp.org + timezone: + type: string + example: CET-1CEST,M3.5.0,M10.5.0/3 + responses: + '200': + description: Successful response + content: + application/json: {} + /api/wifi: + get: + tags: + - DeviceAPI + summary: Wifi + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + ap: + type: boolean + example: true + apSSID: + type: string + example: DerEffi's Pixelart + apPassword: + type: string + example: '00000000' + apIP: + type: string + example: '192.168.4.1' + connect: + type: boolean + example: false + setupComplete: + type: boolean + example: true + ssid: + type: string + example: 'MyWifiNetwork' + ip: + type: string + example: '192.168.2.30' + hostname: + type: string + example: 'dereffis-pixelart' + rssi: + type: number + example: -40 + mac: + type: string + example: '00:00:00:00:00:00' + post: + tags: + - DeviceAPI + summary: Wifi + requestBody: + content: + application/json: + schema: + type: object + properties: + ap: + type: boolean + example: true + apSSID: + type: string + example: DerEffi's Pixelart + apPassword: + type: string + example: '00000000' + connect: + type: boolean + example: false + ssid: + type: string + example: 'MyWifiNetwork' + password: + type: string + example: '00000000' + responses: + '200': + description: Successful response + content: + application/json: {} + /api/wifi/available: + get: + tags: + - DeviceAPI + summary: Scan Wifi networks + description: Returns the result from previous scan and starts a new one. Has to be invoked twice at minimum because no scan was performed before the first call. + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + networks: + type: array + items: + type: object + properties: + ssid: + type: string + example: MyWifiNetwork + rssi: + type: number + example: -40 + encryption: + type: number + example: 4 + /api/file: + post: + tags: + - DeviceAPI + summary: File + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + type: + type: string + example: images + description: Either 'webinterface', 'images' or 'firmware'. Prefixes the specified folder automatically to the filepath (see below). + '{{filepath}}': + description: filepath within the selected type above. For images it is the filepath in the 'images/'-folder, so you would specify '001 - dereffi/image.pxart' to upload the image file. + type: string + example: '001 - dereffi/image.pxart' + format: binary + responses: + '202': + description: Successful response + content: + application/json: {} + /api/reset: + post: + tags: + - DeviceAPI + summary: Reset all user defined settings + requestBody: + content: {} + responses: + '200': + description: Successful response + content: + application/json: {} + /api/refresh: + post: + tags: + - DeviceAPI + summary: Refresh Display + requestBody: + content: {} + responses: + '200': + description: Successful response + content: + application/json: {} + /socials: + post: + tags: + - SocialAPI + summary: Request Social Stats + security: + - SocialAPIKeyAuth: [] + requestBody: + content: + application/json: + schema: + type: array + items: + type: object + properties: + t: + type: string + description: Channel type - one of "t", "y" or "i" for twitch, youtube or instagram respectively + example: "t" + c: + type: string + description: Channel name or Username + example: "DerEffi" + d: + type: string + description: Display name on the device + example: "@DerEffi" + example: + - t: t + c: dereffi + d: '@DerEffi' + - t: 'y' + c: jadereffi + d: '@JaDerEffi' + - t: i + c: jadereffi + d: '@JaDerEffi' + responses: + '200': + description: Successful response + content: + application/json: {} \ No newline at end of file diff --git a/docs/Case/backside.svg b/docs/Case/backside.svg new file mode 100644 index 0000000..71200ea --- /dev/null +++ b/docs/Case/backside.svg @@ -0,0 +1,343 @@ + + + + + + + + diff --git a/docs/Case/backside_frame.svg b/docs/Case/backside_frame.svg new file mode 100644 index 0000000..da1c81a --- /dev/null +++ b/docs/Case/backside_frame.svg @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/docs/Case/bottom_frame.svg b/docs/Case/bottom_frame.svg new file mode 100644 index 0000000..3c57a6d --- /dev/null +++ b/docs/Case/bottom_frame.svg @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/docs/Case/bottom_plate.svg b/docs/Case/bottom_plate.svg new file mode 100644 index 0000000..5632f27 --- /dev/null +++ b/docs/Case/bottom_plate.svg @@ -0,0 +1,5099 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Connect toDerEffis Pixelart00000000dereffis-pixelarthttp://192.168.4.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Case/component_layout.svg b/docs/Case/component_layout.svg new file mode 100644 index 0000000..b75aa62 --- /dev/null +++ b/docs/Case/component_layout.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Case/display_frame_adafruit.svg b/docs/Case/display_frame_adafruit.svg new file mode 100644 index 0000000..cc8ce4d --- /dev/null +++ b/docs/Case/display_frame_adafruit.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + diff --git a/docs/Case/display_frame_generic.svg b/docs/Case/display_frame_generic.svg new file mode 100644 index 0000000..0c9f9c0 --- /dev/null +++ b/docs/Case/display_frame_generic.svg @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/docs/Case/display_frame_waveshare.svg b/docs/Case/display_frame_waveshare.svg new file mode 100644 index 0000000..0ad58ec --- /dev/null +++ b/docs/Case/display_frame_waveshare.svg @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/docs/Case/elements/animation.svg b/docs/Case/elements/animation.svg new file mode 100644 index 0000000..436e8e9 --- /dev/null +++ b/docs/Case/elements/animation.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/Case/elements/brightness.svg b/docs/Case/elements/brightness.svg new file mode 100644 index 0000000..807f725 --- /dev/null +++ b/docs/Case/elements/brightness.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Case/elements/diashow.svg b/docs/Case/elements/diashow.svg new file mode 100644 index 0000000..4b1eab3 --- /dev/null +++ b/docs/Case/elements/diashow.svg @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/docs/Case/elements/qrcode-pixelart.dereffi.de.svg b/docs/Case/elements/qrcode-pixelart.dereffi.de.svg new file mode 100644 index 0000000..9fde11b --- /dev/null +++ b/docs/Case/elements/qrcode-pixelart.dereffi.de.svg @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Case/elements/qrcode-wifi.svg b/docs/Case/elements/qrcode-wifi.svg new file mode 100644 index 0000000..477923e --- /dev/null +++ b/docs/Case/elements/qrcode-wifi.svg @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Case/elements/sd-card.svg b/docs/Case/elements/sd-card.svg new file mode 100644 index 0000000..4c6d430 --- /dev/null +++ b/docs/Case/elements/sd-card.svg @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/docs/Case/elements/wifi.svg b/docs/Case/elements/wifi.svg new file mode 100644 index 0000000..86ab2d8 --- /dev/null +++ b/docs/Case/elements/wifi.svg @@ -0,0 +1,18 @@ + + + + + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/docs/Case/support_bracket.svg b/docs/Case/support_bracket.svg new file mode 100644 index 0000000..7c44c3b --- /dev/null +++ b/docs/Case/support_bracket.svg @@ -0,0 +1,50 @@ + + + + + + + + + + diff --git a/pixelart-controller/docs/Pinout-Devkit-c.webp b/docs/Schematics/ESP32-S3-DevkitC-Pinout.webp similarity index 100% rename from pixelart-controller/docs/Pinout-Devkit-c.webp rename to docs/Schematics/ESP32-S3-DevkitC-Pinout.webp diff --git a/images/bitmaps/animation.jpg b/images/bitmaps/animation.jpg new file mode 100644 index 0000000..dd14ef1 Binary files /dev/null and b/images/bitmaps/animation.jpg differ diff --git a/images/bitmaps/arrow_down.jpg b/images/bitmaps/arrow_down.jpg new file mode 100644 index 0000000..304f6c6 Binary files /dev/null and b/images/bitmaps/arrow_down.jpg differ diff --git a/images/bitmaps/arrow_up.jpg b/images/bitmaps/arrow_up.jpg new file mode 100644 index 0000000..61025ad Binary files /dev/null and b/images/bitmaps/arrow_up.jpg differ diff --git a/images/bitmaps/diashow.jpg b/images/bitmaps/diashow.jpg new file mode 100644 index 0000000..93496b7 Binary files /dev/null and b/images/bitmaps/diashow.jpg differ diff --git a/images/bitmaps/icon_instagram.jpg b/images/bitmaps/icon_instagram.jpg new file mode 100644 index 0000000..7d37a76 Binary files /dev/null and b/images/bitmaps/icon_instagram.jpg differ diff --git a/images/bitmaps/icon_instagram_outline.jpg b/images/bitmaps/icon_instagram_outline.jpg new file mode 100644 index 0000000..fe30f74 Binary files /dev/null and b/images/bitmaps/icon_instagram_outline.jpg differ diff --git a/images/overview.jpg b/images/overview.jpg new file mode 100644 index 0000000..8045225 Binary files /dev/null and b/images/overview.jpg differ diff --git a/images/test/canvas-pixelate.html b/images/test/canvas-pixelate.html new file mode 100644 index 0000000..e1f8f86 --- /dev/null +++ b/images/test/canvas-pixelate.html @@ -0,0 +1,98 @@ + + + + \ No newline at end of file diff --git a/pixelart-api/.env.example b/pixelart-api/.env.example index be2bcbb..512bcc1 100644 --- a/pixelart-api/.env.example +++ b/pixelart-api/.env.example @@ -1,8 +1,8 @@ APP_NAME="DerEffi's Pixelart - API" -APP_ENV=local -APP_KEY=base64:aFlsyu39KAHIbRTlB2COTAEL+/VqK/pcVmEEqECJEgI= -APP_DEBUG=true -APP_URL=https://api.pixelart.dereffi.de +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=localhost LOG_CHANNEL=stack LOG_DEPRECATIONS_CHANNEL=null @@ -25,3 +25,6 @@ TWITCH_CLIENT_ID= TWITCH_CLIENT_SECRET= YOUTUBE_API_KEY= + +INSTAGRAM_USERNAME= +INSTAGRAM_PASSWORD= diff --git a/pixelart-api/README.md b/pixelart-api/README.md index 3ed385a..caf6e7c 100644 --- a/pixelart-api/README.md +++ b/pixelart-api/README.md @@ -1,66 +1,31 @@ -

Laravel Logo

+#

Pixelart - Socials API

-

-Build Status -Total Downloads -Latest Stable Version -License -

+## About -## About Laravel +This Laravel App acts as service for the ESP32 to receive data from youtube, twitch and instagram. The authentication against this platforms is completely handled by this App, because of the limitations of the microcontroller. -Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: +## Installation -- [Simple, fast routing engine](https://laravel.com/docs/routing). -- [Powerful dependency injection container](https://laravel.com/docs/container). -- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. -- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). -- Database agnostic [schema migrations](https://laravel.com/docs/migrations). -- [Robust background job processing](https://laravel.com/docs/queues). -- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). +1. Install php on your system. Easiest with a server package like XAMPP or WAMPP also including a database server for later usage/debugging +1. Install php [composer](https://getcomposer.org/) on your system +1. Install package dependencies by executing `composer install` in the app directory +1. Copy the `.env.example` to `.env` and customize the environment settings + - Add a database connection + - Add credentials for a [Twitch App](https://dev.twitch.tv/console/extensions/create) + - Add credentials for a Youtube/GoogleAPIs App. You need to add the capabilities of [YouTube Data API v3](https://developers.google.com/youtube/v3/getting-started) to your project in the google cloud console. + - Add credentials for an instagram user. (No app, can be any user with permissions to view the account you want to display) +1. Generate a new encryption key with `php artisan key:generate` +1. Add an API key for authentication in the database by either manually adding it in the table or using the DatabaaseSeeder: `php artisan db:seed` -Laravel is accessible, powerful, and provides tools required for large, robust applications. +## Deployment -## Learning Laravel +You can host the app like any other [Laravel App](https://laravel.com/docs/9.x/deployment) by simply copying the files in the hosting directory of your webserver by i.e. + - Using a webserver like Apache or Nginx directly + - Using a webserver package like XAMPP or WAMPP + - Deploying on a homeserver like a Raspberry Pi + - Deploying on a webserver/shared webhosting -Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. +# Usage -You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. - -If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. - -## Laravel Sponsors - -We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell). - -### Premium Partners - -- **[Vehikl](https://vehikl.com/)** -- **[Tighten Co.](https://tighten.co)** -- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** -- **[64 Robots](https://64robots.com)** -- **[Cubet Techno Labs](https://cubettech.com)** -- **[Cyber-Duck](https://cyber-duck.co.uk)** -- **[Many](https://www.many.co.uk)** -- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)** -- **[DevSquad](https://devsquad.com)** -- **[Curotec](https://www.curotec.com/services/technologies/laravel/)** -- **[OP.GG](https://op.gg)** -- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)** -- **[Lendio](https://lendio.com)** - -## Contributing - -Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). - -## Code of Conduct - -In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). - -## Security Vulnerabilities - -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. - -## License - -The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). +You can find a [openapi documentation](../docs/API/openapi.yml) and a [Postman Collection](../docs/API/Pixelart.postman_collection.json) about the available endpoints under [/docs/API](../docs/API/openapi.yml). +Make sure to add the `Authorization` Header with the API key from the Installation before makin any calls. diff --git a/pixelart-api/app/Exceptions/Handler.php b/pixelart-api/app/Exceptions/Handler.php index 82a37e4..c3d89c2 100644 --- a/pixelart-api/app/Exceptions/Handler.php +++ b/pixelart-api/app/Exceptions/Handler.php @@ -3,6 +3,9 @@ namespace App\Exceptions; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Support\ItemNotFoundException; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Throwable; class Handler extends ExceptionHandler @@ -34,6 +37,7 @@ class Handler extends ExceptionHandler 'current_password', 'password', 'password_confirmation', + 'token' ]; /** @@ -43,8 +47,32 @@ class Handler extends ExceptionHandler */ public function register() { - $this->reportable(function (Throwable $e) { - // - }); + // + } + + public function render($request, Throwable $e) { + + try { + if($e instanceof NotFoundHttpException) + return response()->json(["message" => "this endpoint does not exist"], 404); + + if($e instanceof ItemNotFoundException) + return response()->json(["message" => "could not find the item specified"], 404); + + if($e instanceof BadRequestException) + return response()->json(["message" => "bad request"], 400); + + if(config("app.debug")) { + return response()->json([ + "message" => $e->getMessage(), + "line" => $e->getLine(), + "file" => $e->getFile(), + "exception" => get_class($e) + ], 500); + } + + } catch(Throwable $e){} + + return response()->json(["message" => "unexpected error"], 500); } } diff --git a/pixelart-api/app/Http/Controllers/SocialStats.php b/pixelart-api/app/Http/Controllers/SocialStats.php index 2cc3439..5bad49d 100644 --- a/pixelart-api/app/Http/Controllers/SocialStats.php +++ b/pixelart-api/app/Http/Controllers/SocialStats.php @@ -2,52 +2,23 @@ namespace App\Http\Controllers; +use DOMDocument; +use DOMXPath; +use Exception; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; -use Spatie\FlareClient\Http\Exceptions\InvalidData; use Illuminate\Support\Facades\Validator; class SocialStats extends Controller { - //staring from get url param - public function get(Request $request) { - - try { - $base64_socials = $request->get('socials'); - if(empty($base64_socials)) - throw new InvalidData(); - - $json_socials = base64_decode($base64_socials); - if(empty($json_socials)) - throw new InvalidData(); - $socials = json_decode($json_socials, true); - if(empty($socials)) - throw new InvalidData(); - - $validator = Validator::make($socials, [ - "*.t" => "required|in:y,t", - "*.c" => "required|min:4|max:60" - ]); - if($validator->fails()) - return response()->json(["message" => "wrong request format. Instead use [{'t': 'y' | 't', 'c': %channelname%}]"], 400); - - $socials = $this->retrieveSocials($socials); - - return response()->json($socials); - } catch(\Exception $e) { - return response()->json(["message" => "get parameter 'socials' is required to have a valid base64 encoded json array of requested channel data"], 400); - } - } - //starting from post body data public function post(Request $request) { - try { $socials = $request->all(); $validator = Validator::make($socials, [ - "*.t" => "required|in:y,t", + "*.t" => "required|in:y,t,i", "*.c" => "required|min:4|max:60" ]); if($validator->fails()) @@ -57,7 +28,7 @@ public function post(Request $request) { return response()->json($socials); } catch(\Exception $e) { - return response()->json(["message" => "post body is required to have a valid json array of requested channel data"], 400); + return response()->json(["message" => "could not process requested channel data"], 500); } } @@ -68,11 +39,11 @@ public function post(Request $request) { * @param array socials An array of socials to retrieve. * * @return An array of socials with the following keys: - * - t: The type of social (y for youtube, t for twitch) + * - t: The type of social (y for youtube, t for twitch, i for instagram) * - c: The channel name * - d: The display name * - f: The number of followers/subs - * - v: The number of views + * - v: The number of views/follows */ private function retrieveSocials(array $socials) { $socials = collect($socials); @@ -81,7 +52,11 @@ private function retrieveSocials(array $socials) { $youtube = $socials->filter(function($social) { return $social["t"] == "y"; })->map(function($social) { - $social["id"] = $this->getYoutubeChannelId($social["c"]); + try { + $social["id"] = $this->getYoutubeChannelId($social["c"]); + } catch(\Exception) { + $social["id"] = ""; + } $social["v"] = Cache::get("youtube_channel_views_".$social["id"]); $social["f"] = Cache::get("youtube_channel_subs_".$social["id"]); @@ -92,14 +67,20 @@ private function retrieveSocials(array $socials) { if(!$youtube->every(function($social) { return $social["v"] != null && $social["f"] != null; })) { - $youtubeResponse = collect($this->requestYoutubeChannelStats($youtube->map(function($social){ return $social["id"]; })->toArray())); + try { + $youtubeResponse = collect($this->requestYoutubeChannelStats($youtube->map(function($social){ return $social["id"]; })->toArray())); + } catch(\Exception) {} } //Try getting twitch socials from cache else request from twitch $twitch = $socials->filter(function($social) { return $social["t"] == "t"; })->map(function($social) { - $social["id"] = $this->getTwitchUserId($social["c"]); + try { + $social["id"] = $this->getTwitchUserId($social["c"]); + } catch(\Exception) { + $social["id"] = ""; + } $social["v"] = Cache::get("twitch_user_views_".$social["id"]); $social["f"] = Cache::get("twitch_user_follower_".$social["id"]) ?? $this->getTwitchFollower($social["id"]); @@ -110,9 +91,30 @@ private function retrieveSocials(array $socials) { if(!$twitch->every(function($social) { return $social["v"] != null; })) { - $twitchResponse = collect($this->requestTwitchViewers($twitch->map(function($social){ return $social["id"]; })->toArray())); + try { + $twitchResponse = collect($this->requestTwitchViewers($twitch->map(function($social){ return $social["id"]; })->toArray())); + } catch(\Exception) {} } + //Try getting instagram socials from cache or else request from instagram + $socials = $socials->map(function($social) { + if($social["t"] == "i") { + $social["f"] = Cache::get("instagram_user_follower_".$social["c"]); + $social["v"] = Cache::get("instagram_user_liked_".$social["c"]); + + if(is_null($social["f"]) && is_null($social["v"])) { + try { + $instagramData = $this->requestInstagramStats($social["c"]); + $social["id"] = $instagramData["id"]; + $social["f"] = $instagramData["f"]; + $social["v"] = $instagramData["v"]; + } catch(\Exception) {} + } + } + + return $social; + }); + //Merge socials return $socials->map(function($social) use ($youtube, $twitch, $youtubeResponse, $twitchResponse) { switch($social["t"]) { @@ -129,14 +131,16 @@ private function retrieveSocials(array $socials) { $social = array_merge($social, $updatedViews); } break; + default: + break; } return [ "t" => $social["t"], "c" => $social["c"], "d" => $social["d"] ?? $social["c"], - "f" => $social["f"] ? $this->formatNumber($social["f"]) : null, - "v" => $social["v"] && $social["v"] != -1 ? $this->formatNumber($social["v"]) : null, + "f" => !empty($social["f"]) && $social["f"] != -1 ? $this->formatNumber($social["f"]) : "0", + "v" => !empty($social["v"]) && $social["v"] != -1 ? $this->formatNumber($social["v"]) : "0", ]; }); } @@ -177,54 +181,6 @@ private function getTwitchToken($fromCache = true) { } } - /** - * It takes an array of twitch usernames and returns an array of objects containing the username - * and id of each user - * - * @param array logins An array of twitch usernames - * - * @return An array of user objects with the login and id of the user. - */ - private function requestTwitchUserIds(array $logins) { - - $url = "https://api.twitch.tv/helix/users"; - - $counter = 0; - foreach($logins as $login) { - $url .= $counter == 0 ? "?login=".$login : "&login=".$login; - $counter++; - } - - $response = Http::withHeaders([ - "Authorization" => "Bearer ".$this->getTwitchToken(), - "Client-Id" => config("socials.twitch.client_id") - ])->get($url); - - if($response->status() == 401) { - $response = Http::withHeaders([ - "Authorization" => "Bearer ".$this->getTwitchToken(true), - "Client-Id" => config("socials.twitch.client_id") - ])->get($url); - } - - $response->throwUnlessStatus(200); - - $validator = Validator::make($response->collect()->toArray(), [ - "data" => "required|array", - "data.*.id" => "required", - "data.*.login" => "required" - ]); - if($validator->fails()) - return throw new AuthorizationException("Failed to retrive data from twitch"); - - return collect($response["data"]).map(function($user) { - return [ - "login" => $user["login"], - "id" => $user["id"], - ]; - }); - } - /** * It gets the user id of a twitch user * @@ -272,6 +228,9 @@ private function getTwitchUserId($login, $fromCache = true) { * @return The number of followers the user has. */ private function getTwitchFollower($userId, $fromCache = true) { + if(empty($userId)) + return 0; + if(!$fromCache) Cache::forget("twitch_user_follower_".$userId); @@ -316,10 +275,15 @@ private function requestTwitchViewers(array $userIds) { $counter = 0; foreach($userIds as $userId) { - $url .= $counter == 0 ? "?user_id=".$userId : "&user_id=".$userId; - $counter++; + if(!empty($userId)) { + $url .= $counter == 0 ? "?user_id=".$userId : "&user_id=".$userId; + $counter++; + } } + if($counter == 0) + return []; + $response = Http::withHeaders([ "Authorization" => "Bearer ".$this->getTwitchToken(), "Client-Id" => config("socials.twitch.client_id") @@ -344,15 +308,15 @@ private function requestTwitchViewers(array $userIds) { } foreach($userIds as $userId) { - Cache::put("twitch_user_views_".$userId, -1, 60); + Cache::put("twitch_user_views_".$userId, -1, 300); } - if(!$response["data"]) + if(empty($response["data"])) return []; return collect($response["data"])->map(function($user) { - Cache::put("twitch_user_views_".$user["user_id"], $user["viewer_count"], 60); + Cache::put("twitch_user_views_".$user["user_id"], $user["viewer_count"], 30); return [ "v" => $user["viewer_count"], @@ -373,7 +337,12 @@ private function requestTwitchViewers(array $userIds) { * - f */ private function requestYoutubeChannelStats($channelIds) { - $response = Http::get("https://www.googleapis.com/youtube/v3/channels?part=statistics&id=".join(",", $channelIds)."&key=".config("socials.youtube.api_key")); + + $filteredIds = array_filter($channelIds, function($id) { return !empty($id); }); + if($filteredIds == 0) + return []; + + $response = Http::get("https://www.googleapis.com/youtube/v3/channels?part=statistics&id=".join(",", $filteredIds)."&key=".config("socials.youtube.api_key")); $response->throwUnlessStatus(200); @@ -409,6 +378,9 @@ private function requestYoutubeChannelStats($channelIds) { * @return The channel ID of the channel name provided. */ private function getYoutubeChannelId($channelName, $fromCache = true): string { + if(!$fromCache) + Cache::forget("youtube_channel_id_".$channelName); + return Cache::rememberForever("youtube_channel_id_".$channelName, function() use ($channelName) { $response = Http::get("https://www.googleapis.com/youtube/v3/search?part=id&maxResults=1&q=".$channelName."&key=".config("socials.youtube.api_key")); @@ -425,6 +397,118 @@ private function getYoutubeChannelId($channelName, $fromCache = true): string { }); } + /** + * It requests the Instagram API for the user's ID, followers and following count + * + * @param username The username of the user you want to get the stats of. + * + * @return An array with the user's id, followers, and following. + */ + private function requestInstagramStats($username) { + $response = Http::withHeaders([ + "User-Agent" => "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 118.0.0.25.121 (iPhone11,8; iOS 13_1_3; en_US; en-US; scale=2.00; 828x1792; 180988914)" + ])->withCookies([ + "sessionid" => Cache::get("instagram_sessionid") + ], "instagram.com")->get("https://i.instagram.com/api/v1/users/web_profile_info/?username=".$username); + + if(!$response->getHeaders()["Content-Type"] || str_starts_with($response->getHeaders()["Content-Type"][0], "text/html")) { + $document = new DOMDocument(); + $document->loadHTML($response->getBody()->getContents()); + $result = (new DOMXPath($document))->query('//title'); + + if($result->length < 1 || str_contains(strtolower($result->item(0)->nodeValue), "login")) { + Cache::delete("instagram_sessionid"); + $response = Http::withHeaders([ + "User-Agent" => "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 118.0.0.25.121 (iPhone11,8; iOS 13_1_3; en_US; en-US; scale=2.00; 828x1792; 180988914)" + ])->withCookies([ + "sessionid" => Cache::rememberForever("instagram_sessionid", function() { return $this->authInstagram(); }) + ], "instagram.com")->get("https://i.instagram.com/api/v1/users/web_profile_info/?username=".$username); + } else { + throw new Exception("Could not authorize against instagram"); + } + } + + if($response->status() == 404) { + Cache::put("instagram_user_id_".$username, -1, 21600); + Cache::put("instagram_user_follower_".$username, -1, 21600); + Cache::put("instagram_user_liked_".$username, -1, 21600); + } + + $response->throwUnlessStatus(200); + $responseData = $response->collect()->toArray(); + + $validator = Validator::make($responseData, [ + "data.user.id" => "required", + "data.user.edge_followed_by.count" => "required|numeric", + "data.user.edge_follow.count" => "required|numeric", + "data.user.edge_owner_to_timeline_media.edges" => "sometimes|array", + "data.user.edge_owner_to_timeline_media.edges.*.node.edge_liked_by.count" => "sometimes|numeric", + ]); + if($validator->fails()) + return throw new AuthorizationException("Failed to retrive data from twitch"); + + Cache::forever("instagram_user_id_".$username, $responseData["data"]["user"]["id"]); + Cache::put("instagram_user_follower_".$username, $responseData["data"]["user"]["edge_followed_by"]["count"], 1800); + + $likes = 0; + if(!empty($responseData["data"]["user"]["edge_owner_to_timeline_media"]["edges"])) + $likes = $responseData["data"]["user"]["edge_owner_to_timeline_media"]["edges"][0]["node"]["edge_liked_by"]["count"]; + Cache::put("instagram_user_liked_".$username, $likes, 1800); + + return [ + "id" => $responseData["data"]["user"]["id"], + "f" => $responseData["data"]["user"]["edge_followed_by"]["count"], + "v" => $likes, + ]; + } + + /** + * It gets the sessionid cookie token from the login page with username and password + * + * @return string The session cookie value. + */ + private function authInstagram(): string { + $username = config("socials.instagram.username"); + $password = config("socials.instagram.password"); + + if(empty($username) || empty($password)) + throw new AuthorizationException(); + + $baseRequest = Http::withHeaders([ + "User-Agent" => "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 118.0.0.25.121 (iPhone11,8; iOS 13_1_3; en_US; en-US; scale=2.00; 828x1792; 180988914)" + ])->get("https://www.instagram.com/"); + + + $html = $baseRequest->getBody()->getContents(); + + preg_match('/\\\"csrf_token\\\":\\\"(.*?)\\\"/', $html, $matches); + + if (!isset($matches[1])) { + throw new Exception('Unable to extract JSON data'); + } + + $csrfToken = $matches[1]; + + $query = Http::withHeaders([ + 'cookie' => 'ig_cb=1; csrftoken=' . $csrfToken, + 'referer' => "https://www.instagram.com/", + 'x-csrftoken' => $csrfToken, + "User-Agent" => "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 118.0.0.25.121 (iPhone11,8; iOS 13_1_3; en_US; en-US; scale=2.00; 828x1792; 180988914)", + 'accept-language' => "en-EN", + ])->asForm()->post("https://www.instagram.com/accounts/login/ajax/", [ + 'username' => $username, + 'enc_password' => '#PWD_INSTAGRAM_BROWSER:0:' . time() . ':' . $password, + ]); + + $query->throwUnlessStatus(200); + + $sessionCookie = $query->cookies->getCookieByName('sessionid'); + if(empty($sessionCookie)) + throw new Exception(); + + return $sessionCookie->getValue(); + } + /** * It takes a number and returns a string with the number formatted with a thousand separator * @@ -434,6 +518,10 @@ private function getYoutubeChannelId($channelName, $fromCache = true): string { * comma as the decimal separator. */ private function formatNumber($number): string { - return number_format($number, 0, ",", "."); + try { + return number_format($number, 0, ",", "."); + } catch(\Exception) { + return 0; + } } } diff --git a/pixelart-api/app/Http/Kernel.php b/pixelart-api/app/Http/Kernel.php index d6c4b5b..d833d3d 100644 --- a/pixelart-api/app/Http/Kernel.php +++ b/pixelart-api/app/Http/Kernel.php @@ -23,6 +23,10 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ]; + protected $middlewarePriority = [ + \App\Http\Middleware\JsonMiddleware::class, + ]; + /** * The application's route middleware groups. * @@ -41,6 +45,7 @@ class Kernel extends HttpKernel 'api' => [ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', + \App\Http\Middleware\JsonMiddleware::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; diff --git a/pixelart-api/app/Http/Middleware/JsonMiddleware.php b/pixelart-api/app/Http/Middleware/JsonMiddleware.php new file mode 100644 index 0000000..ea2235c --- /dev/null +++ b/pixelart-api/app/Http/Middleware/JsonMiddleware.php @@ -0,0 +1,24 @@ +headers->set('Accept', 'application/json'); + $request->headers->set('Content-Type', 'application/json'); + + return $next($request); + } +} diff --git a/pixelart-api/config/socials.php b/pixelart-api/config/socials.php index 6a5dbde..105105b 100644 --- a/pixelart-api/config/socials.php +++ b/pixelart-api/config/socials.php @@ -7,5 +7,9 @@ ], "youtube" => [ "api_key" => env("YOUTUBE_API_KEY") + ], + "instagram" => [ + "username" => env("INSTAGRAM_USERNAME"), + "password" => env("INSTAGRAM_PASSWORD") ] ]; diff --git a/pixelart-api/routes/api.php b/pixelart-api/routes/api.php index b1ae2ec..1eb9664 100644 --- a/pixelart-api/routes/api.php +++ b/pixelart-api/routes/api.php @@ -1,7 +1,6 @@ group(function() { - Route::get("/socials", [SocialStats::class, "get"]); Route::post("/socials", [SocialStats::class, "post"]); }); diff --git a/pixelart-controller/.gitignore b/pixelart-controller/.gitignore index c92ba5a..27add3e 100644 --- a/pixelart-controller/.gitignore +++ b/pixelart-controller/.gitignore @@ -3,4 +3,5 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch -src/main.h \ No newline at end of file +src/main.h +data/*.conf \ No newline at end of file diff --git a/pixelart-controller/Readme.md b/pixelart-controller/Readme.md new file mode 100644 index 0000000..8979ff7 --- /dev/null +++ b/pixelart-controller/Readme.md @@ -0,0 +1,48 @@ +#

Pixelart - Controller

+ +## About + +This is the firmware for the controller of the led matrix. + +## Hardware compatibility + +For the firmware you need an ESP32-S3 specifically. For other versions either RAM or GPIO Pins are missing. I used a DevKit with the Onboard LED on Pin 48 for this project (DevkitC V1), by swapping RGB-LED Pin on your devboard with pin 48 in [main.h](./src/main.h) and your hardware you should be able to use different devkits as well. + +The project should work with any SPI SD-Card reader connected to the according pins and any I2C RTC Module. The device should work without both of these, but can't use the webinterface and displaying images if you are missing an sd card and you would need to set the clock manually every time you start the device or have it connected to wifi to update itself on start if you don't use any RTC module. + +For the display, I used a 64x64 LED Matrix connected via HUB75E interface. Although you should be able to use any HUB75 display, you will have to customize a big part of the code when using other dimensions, since I hardcoded most of the bitmaps and positions to that resolution. + +## Wiring + +If you have the components described above, you can wire them according to the main.h file. See [Customization](#customization) for more information on that below. + +## Installation + +Development is done using [Visual Studio Code](https://code.visualstudio.com/) with the [PlatformIO extension](https://platformio.org/install/ide?install=vscode). The dependencies should be installed on your first build. + +Under `src/` copy the `main.h.example` and rename it to `main.h`. + +## Customization + +Be aware that any changes you make might prevent you from using [Updates](#updates). For more information see below. + +In the `src/main.h` file you can set the Pins used by the controller for the attached devices (SPI / I2C / HUB75E and Interrupts) as well as the factory settings for wifi, time and the socials API. If you plan to use the socials functions, make sure to deploy the socials-api app and setting the server and api key. The social channels can be configured by the webinterface later. + +In the `platform.ini` file you can change the build option `DPIXEL_COLOUR_DEPTH_BITS` for the devkit-c profile. Even though the colors might not be optimal, the sweetspot for me was to set it to `5`. 6 or more requires the panel to run at a lower refresh rate, with induces the typical LED flickering on cameras and I think you can even see it by eye. + +You can override the socials API Key and hosted Wifi password on your device by copying the two files in the `data` folder and removing `.example` from the filename. The configuration from these files are prioritised over everything else and therefore the only ones not changing with any updates you perform. If you want to update these values, edit the files and repeat the first step under [Deployment](#deployment). + +## Deployment + +1. Deploy the configuration files to your ESP32-S3 by selecting the build profile (`devkit-c` if you are using my provided configuration) in the PlatformIO menu. Under `Platform` first Build the Filesystem Image and then Upload it to your device connected via Serial/USB. This uploads the data folder to the devices filesystem. +1. Deploy the firmware under `General > Upload` the same way. + +## Updates + +! Updates will override any customization except from files of the `data` folder. User settings made via webinterface will be kept until factory reset ! + +Although I plan to revisit this project, since there are some issues I still want to resolve. I can't make any promises on when or if updates will be made or if the hardware configuration will stay the same, since there are some "janky" solutions in place right now, but if you plan to deploy this project yourself, you might be able to use the update functionalities either via OTA or SD-Card on your own by changing the update server. + +When using your own update server, make sure to place the `version.json` file for the version metadata alongside your `firmware.bin` inside `www.your-update.server/firmware/`. + +On more information on how to perform updates visit the webinterface startpage or [http://pixelart.dereffi.de/](http://pixelart.dereffi.de/#/#updates) \ No newline at end of file diff --git a/pixelart-controller/data/socials.conf.example b/pixelart-controller/data/socials.conf.example new file mode 100644 index 0000000..6c5acc7 --- /dev/null +++ b/pixelart-controller/data/socials.conf.example @@ -0,0 +1 @@ +f8f1302b-3e0d-48cd-a6c4-5ffe18bbb6d6 \ No newline at end of file diff --git a/pixelart-controller/data/wifi.conf.example b/pixelart-controller/data/wifi.conf.example new file mode 100644 index 0000000..c43d093 --- /dev/null +++ b/pixelart-controller/data/wifi.conf.example @@ -0,0 +1 @@ +00000000 \ No newline at end of file diff --git a/pixelart-controller/docs/Pinout-Devkit-V1.webp b/pixelart-controller/docs/Pinout-Devkit-V1.webp deleted file mode 100644 index 6fd5c80..0000000 Binary files a/pixelart-controller/docs/Pinout-Devkit-V1.webp and /dev/null differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/.github/dependabot.yml b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/.github/dependabot.yml new file mode 100644 index 0000000..90e05c4 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/.github/workflows/pio_build.yml b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/.github/workflows/pio_build.yml new file mode 100644 index 0000000..379983d --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/.github/workflows/pio_build.yml @@ -0,0 +1,65 @@ +# Build examples with Platformio +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions +# https://docs.platformio.org/en/latest/integration/ci/github-actions.html + +name: PlatformIO CI + +on: + push: + branches: [ master, dev ] + paths-ignore: + - '**.md' + - 'doc/**' + - '.github/**' + pull_request: + branches: [ master, dev ] + paths-ignore: + - '**.md' + - 'doc/**' + - '.github/**' + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + framework: ["Arduino", "IDF"] + no_gfx: ["", -DNO_GFX] +# no_fast_functions: ["", -DNO_FAST_FUNCTIONS] +# no_cie1931: ["", -DNO_CIE1931] +# virtual_panel: ["", -DVIRTUAL_PANE] + example: + - "examples/PIO_TestPatterns" +# exclude: +# - no_fast_functions: "" +# virtual_panel: -DVIRTUAL_PANE + + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Cache pip and platformio + uses: actions/cache@v3 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + key: ${{ runner.os }}-pio + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install Platformio + run: pip install --upgrade platformio + - name: Run PlatformIO CI (Arduino) + if: ${{ matrix.framework == 'Arduino'}} + env: + PLATFORMIO_BUILD_FLAGS: ${{ matrix.no_gfx }} ${{ matrix.no_fast_functions }} ${{ matrix.no_cie1931 }} ${{ matrix.virtual_panel }} + PLATFORMIO_CI_SRC: ${{ matrix.example }} + run: pio ci -e esp32 -c ${{ matrix.example }}/platformio.ini + - name: Run PlatformIO CI (ESP-IDF) + if: ${{ matrix.framework == 'IDF'}} + env: + PLATFORMIO_BUILD_FLAGS: -DIDF_BUILD ${{ matrix.no_gfx }} ${{ matrix.no_fast_functions }} ${{ matrix.no_cie1931 }} ${{ matrix.virtual_panel }} + # pio ci doesn't use our sdkconfig, so we have to use pio run + run: pio run -d ${{ matrix.example }} -e esp32idf -c ${{ matrix.example }}/platformio.ini diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/CMakeLists.txt b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/CMakeLists.txt new file mode 100644 index 0000000..65b7fff --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/CMakeLists.txt @@ -0,0 +1,45 @@ +# HUB75 RGB LED matrix library utilizing ESP32 DMA Engine +# https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA +# MIT License + +cmake_minimum_required(VERSION 3.5) +idf_build_get_property(target IDF_TARGET) + +if(ARDUINO_ARCH_ESP32) + list(APPEND arduino_build arduino Adafruit-GFX-Library) +else() + list(APPEND esp_idf_build esp_lcd driver) +endif() +idf_component_register(SRCS "src/platforms/esp32/esp32_i2s_parallel_dma.cpp" "src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp" "src/ESP32-HUB75-MatrixPanel-leddrivers.cpp" + src/platforms/${target}/gdma_lcd_parallel16.cpp + INCLUDE_DIRS "./src" + REQUIRES ${arduino_build} ${esp_idf_build} + ) + +# In case you are running into issues with "missing" header files from 3rd party libraries +# you can add them to the REQUIRES section above. If you use some of the build options below +# you probably want to remove (NO_GFX) or replace Adafruit-GFX-Library (USE_GFX_ROOT) + +# Example to build with USE_GFX_ROOT or NO_GFX / just uncomment the appropriate line +# target_compile_options(${COMPONENT_TARGET} PUBLIC -DUSE_GFX_ROOT) +# target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX) + +# esp-idf does not have any GFX library support yet, so we need to define NO_GFX +if(ARDUINO_ARCH_ESP32) +else() + target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX) + if(${target} STREQUAL "esp32s3") + # Don't enable PSRAM based framebuffer just because it's an S3. + # This is an advanced option and should only be used with an S3 with Octal-SPI RAM. + # target_compile_options(${COMPONENT_TARGET} PUBLIC -DSPIRAM_FRAMEBUFFER) + target_compile_options(${COMPONENT_TARGET} PUBLIC) + endif() +endif() + +# You can also use multiple options like this +# target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX -DNO_FAST_FUNCTIONS) + +# All options can be found here: +# https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/blob/master/doc/BuildOptions.md + +project(ESP32-HUB75-MatrixPanel-I2S-DMA) diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/LICENSE.txt b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/LICENSE.txt new file mode 100644 index 0000000..8cec4d5 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2032 Faptastic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/README.md new file mode 100644 index 0000000..39099c1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/README.md @@ -0,0 +1,234 @@ +# HUB75 RGB LED matrix panel library utilizing ESP32 DMA + +__[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [![PlatformIO CI](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/pio_build.yml/badge.svg)](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/pio_build.yml) + +**Table of Content** + +- [Introduction](#introduction) + * [Features](#features) + * [ESP32 variants supported](#esp32-variants-supported) + * [Required memory](#required-memory) + * [Supported Panels](#supported-panels) + * [Panel driver chips known to be working well](#driver-chips-known-to-be-working-well) + * [Unsupported Panels](#unsupported-panels) +- [Getting Started](#getting-started) + * [1. Library Installation](#1-library-installation) + * [2. Wiring the ESP32 to an LED Matrix Panel](#2-wiring-the-esp32-to-an-led-matrix-panel) + * [3. Run a Test Sketch](#3-run-a-test-sketch) +- [Further Information](#further-information) + * [Can I chain panels?](#can-i-chain-panels) + * [Can I use with a larger panel (i.e. 64x64px square panel)?](#can-i-use-with-a-larger-panel-ie-64x64px-square-panel) + * [Adjusting Panel Brightness](#adjusting-panel-brightness) + * [Build-time options](#build-time-options) + * [Latch blanking](#latch-blanking) + * [Power, Power and Power!](#power-power-and-power) + * [Inspiration](#inspiration) + * [Cool uses of this library](#cool-uses-of-this-library) +- [Thank you!](#thank-you) + +# Introduction +* This is an ESP32 Arduino/IDF library for HUB75 / HUB75E RGB LED panels. +* This library 'out of the box' (mostly) supports HUB75 panels where TWO rows/lines are updated in parallel... referred to as 'two scan' panels within this library's documentation. +* 'Four scan' panels are also supported - but please refer to the Four Scan Panel example. +* The library uses the DMA functionality provided by the ESP32's 'LCD Mode' for faster output. + +## Features +- **Low CPU overhead** - Pixel data is sent directly with the use of hardware-backed DMA, no CPU involvement +- **Fast** - Updating pixel data involves only bit-wise logic over DMA buffer memory, no pins manipulation or blocking IO +- **Full screen BCM** - Library utilizes [binary-code modulation](http://www.batsocks.co.uk/readme/art_bcm_5.htm) to render pixel color depth / brightness over the entire matrix to give reasonable colour depth +- **Variable color depth** - Up to TrueColor 24 bits output is possible depending on matrix size/refresh rate required +- **CIE 1931** luminance [correction](https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/) (aka natural LED dimming) implemented +- **Adafruit GFX API** - Library can be built with AdafruitGFX, simplified GFX or without a GFX API at all + +## ESP32 variants supported +* Original ESP32 - That being the ESP-WROOM-32 module with ESP32‑D0WDQ6 chip from ~2017. +* ESP32-S2; and +* ESP32-S3 + +RISC-V ESP32's (like the C3) are not supported as they do not have the hardware 'LCD mode' required for this library. + +## Required memory +"*What's the price for those features?*" - It's [memory](/doc/memcalc.md), you pay it all by precious MCU's internal memory (SRAM) for the DMA buffer. + +A typical 64x32px panel at 24bpp colour uses about 20kB of internal memory. + +Please use the ['Memory Calculator'](/doc/memcalc.md) to see what is *typically* achievable with the typical ESP32. ![Memory Calculator](doc/memcalc.jpg) + +For the ESP32-S3 only, you can use SPIRAM/PSRAM to drive the HUB75 DMA buffer when using an ESP32-S3 with **OCTAL SPI-RAM (PSTRAM)** (i.e. ESP32 S3 N8R8 variant). However, due to bandwidth limitations, the maximum output frequency is limited to approx. 13Mhz, which will limit the real-world number of panels that can be chained without flicker. Please do not use PSRAM as the DMA buffer if using QUAD SPI (Q-SPI), as it's too slow. + +To enable PSRAM support on the ESP32-S3, refer to [the build options](/doc/BuildOptions.md) to enable. + +For all other ESP32 variants (like the most popular ‘original’ ESP32), [only *internal* SRAM can be used](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/55), so you will be limited to the ~200KB or so of 'free' SRAM (because of the memory used for your sketch amongst other things) regardless of how many megabytes of SPIRAM/PSRAM you may have connected. + + +## Supported panels +### Parallel scan lines +* 'Two scan' panels where **two** rows/lines are updated in parallel. + * 64x32 (width x height) 'Indoor' panels, such as this [typical RGB panel available for purchase](https://www.aliexpress.com/item/256-128mm-64-32-pixels-1-16-Scan-Indoor-3in1-SMD2121-RGB-full-color-P4-led/32810362851.html). Often also referred to as 1/16 'scan panel' as every 16th row is updated in parallel (hence why I refer to it as 'two scan') + * 64x64 pixel 1/32 Scan LED Matrix 'Indoor' Panel + +* 'Four scan' panels where **four** rows/lines are updated in parallel. + * 32x16 pixel 1/4 Scan LED Matrix 'Indoor' Panel using an ingenious workaround as demonstrated in the Four_Scan_Panel example. + * 126x64 [SM5266P](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164) + +Ones interested in internals of such matrices could find [this article](https://www.sparkfun.com/news/2650) useful. + +Due to the high-speed optimized nature of this library, only specific panels are supported. Please do not raise issues with respect to panels not supported on the list below. + +![Panel Scan Types](doc/ScanRateGraphic.jpg) + +### Driver chips known to be working well + +* ICND2012 +* [RUC7258](http://www.ruichips.com/en/products.html?cateid=17496) +* FM6126A AKA ICN2038S, [FM6124](https://datasheet4u.com/datasheet-pdf/FINEMADELECTRONICS/FM6124/pdf.php?id=1309677) (Refer to [PatternPlasma](/examples/2_PatternPlasma) example on how to use.) +* SM5266P + +## Unsupported chips +* [SM1620B](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/416) +* RUL5358 / SHIFTREG_ABC_BIN_DE based panels are not supported. +* ICN2053 / FM6353 based panels - Refer to [this library](https://github.com/LAutour/ESP32-HUB75-MatrixPanel-DMA-ICN2053), which is a fork of this library ( [discussion link](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/324)). +* Any other panel not listed above. + +Please use an [alternative library](https://github.com/2dom/PxMatrix) if you bought one of these. + +# Getting Started +## 1. Library Installation + +* Dependancy: You will need to install Adafruit_GFX from the "Library > Manage Libraries" menu. +* Install this library from the Arduino Library manager. + +Library also tested to work fine with PlatformIO, install into your PlatformIO projects' lib/ folder as appropriate. Or just add it into [platformio.ini](/doc/BuildOptions.md) [lib_deps](https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-deps) section. + +## 2. Wiring the ESP32 to an LED Matrix Panel + +Refer to the '*default-pins.hpp' file within the [applicable platforms folder](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/tree/master/src/platforms). + +``` +If you want to change the GPIO mapping at runtime, simply provide the wanted pin mapping as part of the class initialization structure. For example, in your sketch have something like the following: + +// Change these to whatever suits +#define R1_PIN 25 +#define G1_PIN 26 +#define B1_PIN 27 +#define R2_PIN 14 +#define G2_PIN 12 +#define B2_PIN 13 +#define A_PIN 23 +#define B_PIN 19 +#define C_PIN 5 +#define D_PIN 17 +#define E_PIN -1 // required for 1/32 scan panels, like 64x64px. Any available pin would do, i.e. IO32 +#define LAT_PIN 4 +#define OE_PIN 15 +#define CLK_PIN 16 + +HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN}; +HUB75_I2S_CFG mxconfig( + 64, // Module width + 32, // Module height + 2, // chain length + _pins, // pin mapping +); +dma_display = new MatrixPanel_I2S_DMA(mxconfig); +``` + +Make sure you also connect one of the HUB75 interfaces ground pins to a ground pin of the ESP32, otherwise you may get electrical artefacts on LED Matrix Panel. + +Various people have created PCBs for which one can simply connect an ESP32 to a PCB, and then the PCB to the HUB75 connector, such as: + +* Brian Lough's [ESP32 I2S Matrix Shield](http://blough.ie/i2smat/) +* Charles Hallard's [WeMos Matrix Shield](https://github.com/hallard/WeMos-Matrix-Shield-DMA) +* Bogdan Sass's [Morph Clock Shield](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/110#discussioncomment-861152) + +Please contact or order these products from the respective authors. + +### Can I use with a larger panel (i.e. 64x64px square panel)? +If you want to use with a 64x64 pixel panel (typically a HUB75*E* panel) you MUST configure a valid *E_PIN* to your ESP32 and connect it to the E pin of the HUB75 panel! Hence the 'E' in 'HUB75E' + + +## 3. Run a Test Sketch +Below is a bare minimum sketch to draw a single white dot in the top left. You must call begin() before you call ANY pixel-drawing (fonts, lines, colours etc.) function of the MatrixPanel_I2S_DMA class. + +Once this is working, refer to the [PIO Test Patterns](/examples/PIO_TestPatterns) example. This sketch draws simple colors/lines/gradients over the entire matrix and it could help to troubleshoot various issues with ghosting, flickering, etc... + +Note: Requires the use of [PlatformIO](https://platformio.org/), which you should probably use if you aren't already. + +# Further information +## Can I chain panels? +Yes! + +[Horizontal](https://user-images.githubusercontent.com/12006953/122657476-cd358d00-d15b-11eb-9c6c-99b61378c56a.mp4) + +For example: If you want to chain two of these horizontally to make a 128x32 panel you can do so by connecting the panels in series using the HUB75 ribbon cable. Than you must provide proper configuration structure to the class constructor letting it know that you use "one long virtual matrix chain". Refer to [Pattern Plasma](/examples/2_PatternPlasma/) example for all the details about configuration setup. + +Finally, if you wanted to chain 4 x (64x32px) panels to make 128x64px display (essentially a 2x2 grid of 64x32 LED Matrix modules), a little more magic will be required. Refer to the [Chained Panels](examples/ChainedPanels/) example. + +Resolutions beyond 128x64 are more likely to result in crashes due to [memory](/doc/i2s_memcalc.md) constraints etc. You are on your own after this point - PLEASE do not raise issues about this, the library can't magically defeat the SRAM memory constraints of the ESP32. + +![ezgif com-video-to-gif](https://user-images.githubusercontent.com/12006953/89837358-b64c0480-db60-11ea-870d-4b6482068a3b.gif) + +## Adjusting Panel Brightness + +By default you should not need to change / set the brightness value (which is 128 or 50%) as it should be sufficient for most purposes. Brightness can be changed by calling `setPanelBrightness(xx)` or `setBrightness8(xx)`. + +The value to pass must be a number between 0 (for a black screen) and 255 (max brightness). + +Example: +``` +void setup() { +Serial.begin(115200); + dma_display->begin(); // setup the LED matrix + dma_display->setBrightness8(192); //0-255 + dma_display->clearScreen(); +} +``` +![Brightness Samples](https://user-images.githubusercontent.com/55933003/211192894-f90311f5-b6fe-4665-bf26-2f363bb36047.png) + +## Build-time options +Although Arduino IDE does not [seem](https://github.com/arduino/Arduino/issues/421) to offer any way of specifying compile-time options for external libs there are other IDE's (like [PlatformIO](https://platformio.org/)/[Eclipse](https://www.eclipse.org/ide/)) that could use that. Check [Build Options](doc/BuildOptions.md) document for reference. + +## Latch blanking +If you are facing issues with image ghosting when pixels has clones with horizontal offset, than you try to change Latch blanking value. Latch blanking controls for how many clock pulses matrix output is disabled via EO signal before/after toggling LAT signal. It hides row bits transitioning and different panels may require longer times for proper operation. Default value is 1 clock before/after LAT row transition. This could be controlled with `MatrixPanel_I2S_DMA::setLatBlanking(uint8_t v)`. v could be between 1 to 4, default is 1, larger values won't give any benefit other than reducing brightness. + +An example: +``` +dma_display->setLatBlanking(2); +``` + +## Power, Power and Power! +Having a good power supply is CRITICAL, and it is highly recommended, for chains of LED Panels to have a 1000-2000uf capacitor soldered to the back of each LED Panel across the [GND and VCC pins](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-720780463), otherwise you WILL run into issues with 'flashy' graphics whereby a large amount of LEDs are turned on and off in succession (due to current/power draw peaks and troughs). + + - Refer to this guide written for the [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md#a-word-about-power) for an explanation. +- Refer to this [example](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-722691127) issue of what can go wrong with a poor power supply. + + +- Refer to [this comment](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/35#issuecomment-726419862) in regards to certain panels not playing nice with voltages, and a 3.3volt signal that the ESP32 GPIO can only provide. + +## Inspiration +This project was inspired by: +* 'SmartMatrix': https://github.com/pixelmatix/SmartMatrix/tree/teensylc +* Sprite_TM's demo implementation here: https://www.esp32.com/viewtopic.php?f=17&t=3188 + + +## Cool uses of this library +There are a number of great looking LED graphical display projects which leverage this library, these include: +* [128x64 Morph Clock](https://github.com/bogd/esp32-morphing-clock) +* [FFT Audio Visualisation](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/149) +* [Clock, GIF Animator and Audio Visualiser](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/153) +* [Aurora Audio Visualiser](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/188) +* [Big Visualisation](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/155) +* [Clockwise](https://jnthas.github.io/clockwise/) + +# Thank you! +* [Brian Lough](https://www.tindie.com/stores/brianlough/) ([youtube link](https://www.youtube.com/c/brianlough)) for providing code contributions, hardware and suggestions +* [Vortigont](https://github.com/vortigont) for his game changing code contributions and performance optimisations +* [Galaxy Man](https://github.com/Galaxy-Man) for donation of 1/16 scan panels to support the implemenation of led matrix panel chaining (virtual display) support +* [Pipimaxi](https://github.com/Pipimaxi) for the donation of a ESP32-S2 and [Radu](https://github.com/juniorradu) for the donation of an ESP32-S3 to enable support for ESP32 S2/S3's to be tested and implemented. +* [Mark Donners](https://github.com/donnersm) ('The Electronic Engineer' on [youtube](https://www.youtube.com/watch?v=bQ7c9Vlhyp0&t=118s)) for the donation of a 1/8 scan panel to build and test working support of these led matrix panels! +* [PaintYourDragon](https://github.com/PaintYourDragon) for the DMA logic for the ESP32-S3. +* And lots of others, let me know if I've missed you. + +If you want to donate money to the project, please refer to [this discussion](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/349) about it. If you want to donate/buy an LED panel for the library author to improve compatibility and/or testing - please feel free to post in the same [discussion](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/349). + +![It's better in real life](image.jpg) diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/component.mk b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/component.mk new file mode 100644 index 0000000..004b18e --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_INCLUDEDIRS = . diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/BuildOptions.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/BuildOptions.md new file mode 100644 index 0000000..aa31ab7 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/BuildOptions.md @@ -0,0 +1,38 @@ +### Build Options and flags + +This library supports build-time defines to modify its features or enable greater debugging information. Please use the debugging capabilities before raising any issues. + +For example build flags could be set using PlatformIO's .ini file like this + +``` +[env] +framework = arduino +platform = espressif32 +lib_deps = + ESP32 HUB75 LED MATRIX PANEL DMA Display +build_flags = + -DCORE_DEBUG_LEVEL=3 + -DNO_GFX=1 + (etc.....) +``` +Or if using Arduino: 'Tools' menu > 'Core Debug Level' > Select 'Debug' + +... and use the Serial output to see the debug information. + +## Build flags + +| Flag | Description | Note | +| :------------ |---------------|-----| +| **CORE_DEBUG_LEVEL** |Adjust the espressif ESP32 IDF debug level, for which this library leverages to output information on what is going on when allocating memory etc. This will provide detailed information about memory allocations, DMA descriptors setup and color depth [BCM](http://www.batsocks.co.uk/readme/art_bcm_5.htm) |Set value to at least 3 [(Info)](https://iotespresso.com/core-debug-level-in-esp32/) +| **USE_GFX_ROOT** | Use [lightweight](https://github.com/mrfaptastic/Adafruit_GFX_Lite) version of AdafuitGFX, without Adafruit BusIO extensions | You **must** install [Adafruit_GFX_Lite](https://github.com/mrfaptastic/Adafruit_GFX_Lite) library instead of original AdafruitGFX| +| **NO_GFX** | Build without AdafuitGFX API, only native methods supported based on manipulating DMA buffer. I.e. no methods of drawing circles/shapes, typing text or using fonts!!! This might save some resources for applications using it's own internal graphics buffer or working solely with per-pixel manipulation. | Use this if you rely on FastLED, Neomatrix or any other API. For example [Aurora](/examples/AuroraDemo/) effects can work fine w/o AdafruitGFX. | +| **NO_FAST_FUNCTIONS** | Do not build auxiliary speed-optimized functions. Those are used to speed-up operations like drawing straight lines or rectangles. Otherwise lines/shapes are drawn using drawPixel() method. The trade-off for speed is RAM/code-size, take it or leave it ;) | If you are not using AdafruitGFX than you probably do not need this either| +|**NO_CIE1931**|Do not use LED brightness [compensation](https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/) described in [CIE 1931](https://en.wikipedia.org/wiki/CIE_1931_color_space). Normally library would adjust every pixel's RGB888 so that luminance (or brightness control) for the corresponding LED's would appear 'linear' to the human's eye. I.e. a white dot with rgb(128,128,128) would seem to be at 50% brightness between rgb(0,0,0) and rgb(255,255,255). Normally you would like to keep this enabled by default. Not only it makes brightness control "linear", it also makes colours more vivid, otherwise it looks brighter but 'bleached'.|You might want to turn it off in some special cases like: | +| **FORCE_COLOR_DEPTH** |In some cases the library may reduce colour fidelity to increase the refresh rate (i.e. reduce visible flicker). This is most likely to occur with a large chain of panels. However, if you want to force pure 24bpp colour, at the expense of likely noticeable flicker, then set this defined. |Not required in 99% of cases. +| **SPIRAM_FRAMEBUFFER** |Use SPIRAM/PSRAM for the HUB75 DMA buffer and not internal SRAM. ONLY SUPPORTED ON ESP32-S3 VARIANTS WITH OCTAL (not quad!) SPIRAM/PSRAM, as ony OCTAL PSRAM an provide the required data rate / bandwidth to drive the panels adequately.|ONLY SUPPORTED ON ESP32-S3 VARIANTS WITH OCTAL (not quad) SPIRAM/PSRAM + +## Build-time variables + +| Flag | Description | Note | +| :------------ |---------------|-----| +| **PIXEL_COLOR_DEPTH_BITS=8** | Colour depth per pixel in range 2-8. More bit's - more natural colour. But on the other hand every additional bit: | For large chains of panels (i.e. 6 x 64x64 panels) you WILL need to reduce the colour depth, or likely run out of memory. Default is 8 bits per colour per pixel, i.e. True colour 24 bit RGB.

For higher resolutions, from 64x64 and above it is not possible to provide full 24 bits colour without significant flickering OR reducing dynamic range in shadows. In that case using 5-6 bits at high res make very small difference to the human’s eye actually. Refer to the [I2S memcalc](i2s_memcalc.md) for more details. diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/Panel_Chaining_Types.ods b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/Panel_Chaining_Types.ods new file mode 100644 index 0000000..e41e576 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/Panel_Chaining_Types.ods differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/ScanRateGraphic.jpg b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/ScanRateGraphic.jpg new file mode 100644 index 0000000..a875316 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/ScanRateGraphic.jpg differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/ScanRateGraphic.odp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/ScanRateGraphic.odp new file mode 100644 index 0000000..84b98a1 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/ScanRateGraphic.odp differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel (old).odp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel (old).odp new file mode 100644 index 0000000..4e3a066 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel (old).odp differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel.odp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel.odp new file mode 100644 index 0000000..748a211 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel.odp differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel.pdf b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel.pdf new file mode 100644 index 0000000..f6e7157 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/VirtualMatrixPanel.pdf differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/fillrate.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/fillrate.md new file mode 100644 index 0000000..f667209 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/fillrate.md @@ -0,0 +1,27 @@ +## Estimating fillrate + +Here are some results of simple tests on filling DMA buffer with data. +Filling DMA buffer requires lots of memory operations on a bit level rather than doing simple byte/word wide store and copy. And it looks like it's quite a task both for esp32 core and compiler. +I've done this while optimizing loops and bit logic along with testing compiler results. + +So the testbed is: + - Matrix modules: 4 x FM6126A based 64x64 modules chained in 256x64 + +A testpatterns sketch: + - allocating single DMA buffs for 256x64 + - allocating (NUM_LEDS*3) bytes for CRGB buffer + - measuring microseconds for the following calls: + - clearScreen() - full blanking + - fillScreenRGB888() with monochrome/gray colors + - fill screen using drawPixel() + - filling some gradient into CRGB buff + - painting CRGB buff into DMA buff with looped drawPixelRGB888() + - drawing lines + + +||clearScreen()|drawPixelRGB888(), ticks|fillScreen()|fillScreen with a drawPixel()|fillRect() over Matrix|V-line with drawPixel|fast-V-line|H-line with drawPixel|fast-H-line| +|--|--|--|--|--|--|--|--|--|--| +|v1.2.4|1503113 ticks|9244 non-cached, 675 cached|1719 us, 412272 t|47149 us, 11315418 ticks|-|24505 us, 5880209 ticks|-|24200 us|-| +|FastLines|1503113 ticks|1350 non-cached, 405 cached|1677 us, 401198 t|28511 us, 6841440 ticks|10395 us|14462 us, 3469605 ticks|10391 us, 2492743 ticks|14575 us|5180 us, 1242041 ticks| + +to be continued... \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.jpg b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.jpg new file mode 100644 index 0000000..16bb077 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.jpg differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.md new file mode 100644 index 0000000..2525cf5 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.md @@ -0,0 +1,40 @@ +### Memory Calculator + +I've made this [spreadsheet](memcalc.xlsm) to estimate all of the main parameters for ESP32-HUB75-MatrixPanel-DMA lib driving any combination of matrices/chains so that I do not need to reflash it hundreds of times just to check for the debug info about memory. +Be sure to enable embedded macro's to allow refresh rate calculations. + +![](i2scalc.png) +Just fill-in all of the INPUT fields and get the OUTPUTs. + +So there are two main resources used to drive LED matrix + - Memory + - Bus clock speed (resulting in available bandwidth to pump pixel color data) + +And there are lot's of hogs for those: + - matrix resolution (number of pixels) + - number of modules in chain + - pixel color depth + - [BCM](http://www.batsocks.co.uk/readme/art_bcm_5.htm) LSB to MSB transition + - double buffering + +Equalising ones with the others results in **Refresh rate**, + +or (rough approximation) + + +[//]: # (github markdown does not like LaTex formulas) +[//]: # ($$RefreshRate=\frac{resolution \times chain \times (ColorDepth-LSB2MSB)}{ I ^2S _ {clock} }$$) + +So, how to find optimum balance for all of these? Obviously you can't change *resolution* and *chain length*, it is physical characteristics and there is not much you can do about it except cutting off your chain or pushing it to the memory limits. + +There are 3 parameters you can choose from (actually two:) + - **Color Depth** - predefined at [build-time]((/doc/BuildOptions.md)) option + + - I2S clock speed - run-time tunable with a very limited options + +- **LSB-to-MSB** transition - it can't be controlled in any way, library uses it internally trying to balance all of the above + +Using provided table it is possible to estimate all of the parameters before running the library. Besides calculating memory requirements it could help to find **optimum color depth** for your matrix configuration. For higher resolutions default 8 bits could be too much to sustain minimal refresh rate and avoid annoying flickering. So the library would increase MSB transition to keep the balance, thus reducing dynamic range in shadows and dark colors. As a result it is nearly almost the same as just reducing overall color depth. **But** reducing global color depth would also save lot's of precious RAM! +Now it's all up to you to decide :) + +/Vortigont/ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.xlsm b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.xlsm new file mode 100644 index 0000000..ec8bdb0 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/doc/memcalc.xlsm differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/1_SimpleTestShapes/1_SimpleTestShapes.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/1_SimpleTestShapes/1_SimpleTestShapes.ino new file mode 100644 index 0000000..1d2ca75 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/1_SimpleTestShapes/1_SimpleTestShapes.ino @@ -0,0 +1,166 @@ + +// Example sketch which shows how to display some patterns +// on a 64x32 LED matrix +// + +#include + + +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +//MatrixPanel_I2S_DMA dma_display; +MatrixPanel_I2S_DMA *dma_display = nullptr; + +uint16_t myBLACK = dma_display->color565(0, 0, 0); +uint16_t myWHITE = dma_display->color565(255, 255, 255); +uint16_t myRED = dma_display->color565(255, 0, 0); +uint16_t myGREEN = dma_display->color565(0, 255, 0); +uint16_t myBLUE = dma_display->color565(0, 0, 255); + + + +// Input a value 0 to 255 to get a color value. +// The colours are a transition r - g - b - back to r. +// From: https://gist.github.com/davidegironi/3144efdc6d67e5df55438cc3cba613c8 +uint16_t colorWheel(uint8_t pos) { + if(pos < 85) { + return dma_display->color565(pos * 3, 255 - pos * 3, 0); + } else if(pos < 170) { + pos -= 85; + return dma_display->color565(255 - pos * 3, 0, pos * 3); + } else { + pos -= 170; + return dma_display->color565(0, pos * 3, 255 - pos * 3); + } +} + +void drawText(int colorWheelOffset) +{ + + // draw text with a rotating colour + dma_display->setTextSize(1); // size 1 == 8 pixels high + dma_display->setTextWrap(false); // Don't wrap at end of line - will do ourselves + + dma_display->setCursor(5, 0); // start at top left, with 8 pixel of spacing + uint8_t w = 0; + const char *str = "ESP32 DMA"; + for (w=0; wsetTextColor(colorWheel((w*32)+colorWheelOffset)); + dma_display->print(str[w]); + } + + dma_display->println(); + dma_display->print(" "); + for (w=9; w<18; w++) { + dma_display->setTextColor(colorWheel((w*32)+colorWheelOffset)); + dma_display->print("*"); + } + + dma_display->println(); + + dma_display->setTextColor(dma_display->color444(15,15,15)); + dma_display->println("LED MATRIX!"); + + // print each letter with a fixed rainbow color + dma_display->setTextColor(dma_display->color444(0,8,15)); + dma_display->print('3'); + dma_display->setTextColor(dma_display->color444(15,4,0)); + dma_display->print('2'); + dma_display->setTextColor(dma_display->color444(15,15,0)); + dma_display->print('x'); + dma_display->setTextColor(dma_display->color444(8,15,0)); + dma_display->print('6'); + dma_display->setTextColor(dma_display->color444(8,0,15)); + dma_display->print('4'); + + // Jump a half character + dma_display->setCursor(34, 24); + dma_display->setTextColor(dma_display->color444(0,15,15)); + dma_display->print("*"); + dma_display->setTextColor(dma_display->color444(15,0,0)); + dma_display->print('R'); + dma_display->setTextColor(dma_display->color444(0,15,0)); + dma_display->print('G'); + dma_display->setTextColor(dma_display->color444(0,0,15)); + dma_display->print("B"); + dma_display->setTextColor(dma_display->color444(15,0,8)); + dma_display->println("*"); + +} + + +void setup() { + + // Module configuration + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length + ); + + //mxconfig.gpio.e = 18; + //mxconfig.clkphase = false; + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // Display Setup + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->setBrightness8(90); //0-255 + dma_display->clearScreen(); + dma_display->fillScreen(myWHITE); + + // fix the screen with green + dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0)); + delay(500); + + // draw a box in yellow + dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0)); + delay(500); + + // draw an 'X' in red + dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0)); + dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0)); + delay(500); + + // draw a blue circle + dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15)); + delay(500); + + // fill a violet circle + dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15)); + delay(500); + + // fill the screen with 'black' + dma_display->fillScreen(dma_display->color444(0, 0, 0)); + + //drawText(0); + +} + +uint8_t wheelval = 0; +void loop() { + + // animate by going through the colour wheel for the first two lines + drawText(wheelval); + wheelval +=1; + + delay(20); +/* + drawText(0); + delay(2000); + dma_display->clearScreen(); + dma_display->fillScreen(myBLACK); + delay(2000); + dma_display->fillScreen(myBLUE); + delay(2000); + dma_display->fillScreen(myRED); + delay(2000); + dma_display->fillScreen(myGREEN); + delay(2000); + dma_display->fillScreen(myWHITE); + dma_display->clearScreen(); + */ + +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/2_PatternPlasma.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/2_PatternPlasma.ino new file mode 100644 index 0000000..65f85b9 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/2_PatternPlasma.ino @@ -0,0 +1,207 @@ +/* + * Portions of this code are adapted from Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from LedEffects Plasma by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Plasma.cpp?at=default + * Copyright (c) 2013 Robert Atkins + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +// HUB75E pinout +// R1 | G1 +// B1 | GND +// R2 | G2 +// B2 | E +// A | B +// C | D +// CLK| LAT +// OE | GND + +/* Default library pin configuration for the reference + you can redefine only ones you need later on object creation + +#define R1 25 +#define G1 26 +#define BL1 27 +#define R2 14 +#define G2 12 +#define BL2 13 +#define CH_A 23 +#define CH_B 19 +#define CH_C 5 +#define CH_D 17 +#define CH_E -1 // assign to any available pin if using two panels or 64x64 panels with 1/32 scan +#define CLK 16 +#define LAT 4 +#define OE 15 + +*/ + + +#include +#include + +// Configure for your panel(s) as appropriate! +#define PANEL_WIDTH 64 +#define PANEL_HEIGHT 64 // Panel height of 64 will required PIN_E to be defined. +#define PANELS_NUMBER 2 // Number of chained panels, if just a single panel, obviously set to 1 +#define PIN_E 32 + +#define PANE_WIDTH PANEL_WIDTH * PANELS_NUMBER +#define PANE_HEIGHT PANEL_HEIGHT + + +// placeholder for the matrix object +MatrixPanel_I2S_DMA *dma_display = nullptr; + + +uint16_t time_counter = 0, cycles = 0, fps = 0; +unsigned long fps_timer; + +CRGB currentColor; +CRGBPalette16 palettes[] = {HeatColors_p, LavaColors_p, RainbowColors_p, RainbowStripeColors_p, CloudColors_p}; +CRGBPalette16 currentPalette = palettes[0]; + + +CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) { + return ColorFromPalette(currentPalette, index, brightness, blendType); +} + +void setup() { + + Serial.begin(115200); + + Serial.println(F("*****************************************************")); + Serial.println(F("* ESP32-HUB75-MatrixPanel-I2S-DMA DEMO *")); + Serial.println(F("*****************************************************")); + + /* + The configuration for MatrixPanel_I2S_DMA object is held in HUB75_I2S_CFG structure, + pls refer to the lib header file for full details. + All options has it's predefined default values. So we can create a new structure and redefine only the options we need + + // those are the defaults + mxconfig.mx_width = 64; // physical width of a single matrix panel module (in pixels, usually it is always 64 ;) ) + mxconfig.mx_height = 32; // physical height of a single matrix panel module (in pixels, usually almost always it is either 32 or 64) + mxconfig.chain_length = 1; // number of chained panels regardless of the topology, default 1 - a single matrix module + mxconfig.gpio.r1 = R1; // pin mappings + mxconfig.gpio.g1 = G1; + mxconfig.gpio.b1 = B1; // etc + mxconfig.driver = HUB75_I2S_CFG::SHIFT; // shift reg driver, default is plain shift register + mxconfig.double_buff = false; // use double buffer (twice amount of RAM required) + mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M;// I2S clock speed, better leave as-is unless you want to experiment + */ + + /* + For example we have two 64x64 panels chained, so we need to customize our setup like this + + */ + HUB75_I2S_CFG mxconfig; + mxconfig.mx_height = PANEL_HEIGHT; // we have 64 pix heigh panels + mxconfig.chain_length = PANELS_NUMBER; // we have 2 panels chained + mxconfig.gpio.e = PIN_E; // we MUST assign pin e to some free pin on a board to drive 64 pix height panels with 1/32 scan + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can change that + + /* + //Another way of creating config structure + //Custom pin mapping for all pins + HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; + HUB75_I2S_CFG mxconfig( + 64, // width + 64, // height + 4, // chain length + _pins, // pin mapping + HUB75_I2S_CFG::FM6126A // driver chip + ); + + */ + + + // OK, now we can create our matrix object + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // let's adjust default brightness to about 75% + dma_display->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100% + + // Allocate memory and start DMA display + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! I2S memory allocation failed ***********"); + + // well, hope we are OK, let's draw some colors first :) + Serial.println("Fill screen: RED"); + dma_display->fillScreenRGB888(255, 0, 0); + delay(1000); + + Serial.println("Fill screen: GREEN"); + dma_display->fillScreenRGB888(0, 255, 0); + delay(1000); + + Serial.println("Fill screen: BLUE"); + dma_display->fillScreenRGB888(0, 0, 255); + delay(1000); + + Serial.println("Fill screen: Neutral White"); + dma_display->fillScreenRGB888(64, 64, 64); + delay(1000); + + Serial.println("Fill screen: black"); + dma_display->fillScreenRGB888(0, 0, 0); + delay(1000); + + + // Set current FastLED palette + currentPalette = RainbowColors_p; + Serial.println("Starting plasma effect..."); + fps_timer = millis(); +} + +void loop() { + + for (int x = 0; x < PANE_WIDTH; x++) { + for (int y = 0; y < PANE_HEIGHT; y++) { + int16_t v = 0; + uint8_t wibble = sin8(time_counter); + v += sin16(x * wibble * 3 + time_counter); + v += cos16(y * (128 - wibble) + time_counter); + v += sin16(y * x * cos8(-time_counter) / 8); + + currentColor = ColorFromPalette(currentPalette, (v >> 8) + 127); //, brightness, currentBlendType); + dma_display->drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b); + } + } + + ++time_counter; + ++cycles; + ++fps; + + if (cycles >= 1024) { + time_counter = 0; + cycles = 0; + currentPalette = palettes[random(0,sizeof(palettes)/sizeof(palettes[0]))]; + } + + // print FPS rate every 5 seconds + // Note: this is NOT a matrix refresh rate, it's the number of data frames being drawn to the DMA buffer per second + if (fps_timer + 5000 < millis()){ + Serial.printf_P(PSTR("Effect fps: %d\n"), fps/5); + fps_timer = millis(); + fps = 0; + } +} // end loop \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/PatternWave.jpg b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/PatternWave.jpg new file mode 100644 index 0000000..e6ce3c3 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/PatternWave.jpg differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/README.md new file mode 100644 index 0000000..172a99f --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/2_PatternPlasma/README.md @@ -0,0 +1,5 @@ +# Wave Pattern + +Demo of the colours, and the little flicker. + +![It's better in real life](PatternWave.jpg) diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/3_DoubleBuffer/3_DoubleBuffer.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/3_DoubleBuffer/3_DoubleBuffer.ino new file mode 100644 index 0000000..5a41d67 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/3_DoubleBuffer/3_DoubleBuffer.ino @@ -0,0 +1,90 @@ +// Example uses the following configuration: mxconfig.double_buff = true; +// to enable double buffering, which means display->flipDMABuffer(); is required. + +// Bounce squares around the screen, doing the re-drawing in the background back-buffer. +// Double buffering is not always required in reality. + +#include + +MatrixPanel_I2S_DMA *display = nullptr; + +uint16_t myDARK = display->color565(64, 64, 64); +uint16_t myWHITE = display->color565(192, 192, 192); +uint16_t myRED = display->color565(255, 0, 0); +uint16_t myGREEN = display->color565(0, 255, 0); +uint16_t myBLUE = display->color565(0, 0, 255); + +uint16_t colours[5] = { myDARK, myWHITE, myRED, myGREEN, myBLUE }; + +struct Square +{ + float xpos, ypos; + float velocityx; + float velocityy; + boolean xdir, ydir; + uint16_t square_size; + uint16_t colour; +}; + +const int numSquares = 25; +Square Squares[numSquares]; + +void setup() +{ + // put your setup code here, to run once: + delay(1000); + Serial.begin(115200); + delay(200); + + Serial.println("...Starting Display"); + HUB75_I2S_CFG mxconfig; + mxconfig.double_buff = true; // <------------- Turn on double buffer + //mxconfig.clkphase = false; + + // OK, now we can create our matrix object + display = new MatrixPanel_I2S_DMA(mxconfig); + display->begin(); // setup display with pins as pre-defined in the library + + // Create some random squares + for (int i = 0; i < numSquares; i++) + { + Squares[i].square_size = random(2,10); + Squares[i].xpos = random(0, display->width() - Squares[i].square_size); + Squares[i].ypos = random(0, display->height() - Squares[i].square_size); + Squares[i].velocityx = static_cast (rand()) / static_cast (RAND_MAX); + Squares[i].velocityy = static_cast (rand()) / static_cast (RAND_MAX); + + int random_num = random(6); + Squares[i].colour = colours[random_num]; + } +} + +void loop() +{ + + display->flipDMABuffer(); // Show the back buffer, set currently output buffer to the back (i.e. no longer being sent to LED panels) + display->clearScreen(); // Now clear the back-buffer + + delay(16); // <----------- Shouldn't see this clearscreen occur as it happens on the back buffer when double buffering is enabled. + + for (int i = 0; i < numSquares; i++) + { + // Draw rect and then calculate + display->fillRect(Squares[i].xpos, Squares[i].ypos, Squares[i].square_size, Squares[i].square_size, Squares[i].colour); + + if (Squares[i].square_size + Squares[i].xpos >= display->width()) { + Squares[i].velocityx *= -1; + } else if (Squares[i].xpos <= 0) { + Squares[i].velocityx = abs (Squares[i].velocityx); + } + + if (Squares[i].square_size + Squares[i].ypos >= display->height()) { + Squares[i].velocityy *= -1; + } else if (Squares[i].ypos <= 0) { + Squares[i].velocityy = abs (Squares[i].velocityy); + } + + Squares[i].xpos += Squares[i].velocityx; + Squares[i].ypos += Squares[i].velocityy; + } +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/4_OtherShiftDriverPanel.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/4_OtherShiftDriverPanel.ino new file mode 100644 index 0000000..e62cecc --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/4_OtherShiftDriverPanel.ino @@ -0,0 +1,114 @@ +/********************************************************************** + * The library by default supports simple 'shift register' based panels + * with A,B,C,D,E lines to select a specific row, but there are plenty + * of examples of new chips coming on the market that work different. + * + * Please search through the project's issues. For some of these chips + * (you will need to look at the back of your panel to identify), this + * library has workarounds. This can be configured through using one of: + + // mxconfig.driver = HUB75_I2S_CFG::FM6126A; + //mxconfig.driver = HUB75_I2S_CFG::ICN2038S; + //mxconfig.driver = HUB75_I2S_CFG::FM6124; + //mxconfig.driver = HUB75_I2S_CFG::MBI5124; + */ + + +#include +#include +#include + +//////////////////////////////////////////////////////////////////// + +// Output resolution and panel chain length configuration +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +// placeholder for the matrix object +MatrixPanel_I2S_DMA *dma_display = nullptr; + +/////////////////////////////////////////////////////////////// + +// FastLED variables for pattern output +uint16_t time_counter = 0, cycles = 0, fps = 0; +unsigned long fps_timer; + +CRGB currentColor; +CRGBPalette16 palettes[] = {HeatColors_p, LavaColors_p, RainbowColors_p, RainbowStripeColors_p, CloudColors_p}; +CRGBPalette16 currentPalette = palettes[0]; + + +CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) { + return ColorFromPalette(currentPalette, index, brightness, blendType); +} + +void setup(){ + + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length + ); + + // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object + mxconfig.driver = HUB75_I2S_CFG::FM6126A; + //mxconfig.driver = HUB75_I2S_CFG::ICN2038S; + //mxconfig.driver = HUB75_I2S_CFG::FM6124; + //mxconfig.driver = HUB75_I2S_CFG::MBI5124; + + + // OK, now we can create our matrix object + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // If you experience ghosting, you will need to reduce the brightness level, not all RGB Matrix + // Panels are the same - some seem to display ghosting artefacts at lower brightness levels. + // In the setup() function do something like: + + // let's adjust default brightness to about 75% + dma_display->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100% + + // Allocate memory and start DMA display + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! Insufficient memory - allocation failed ***********"); + + fps_timer = millis(); + +} + +void loop(){ + for (int x = 0; x < dma_display->width(); x++) { + for (int y = 0; y < dma_display->height(); y++) { + int16_t v = 0; + uint8_t wibble = sin8(time_counter); + v += sin16(x * wibble * 3 + time_counter); + v += cos16(y * (128 - wibble) + time_counter); + v += sin16(y * x * cos8(-time_counter) / 8); + + currentColor = ColorFromPalette(currentPalette, (v >> 8) + 127); //, brightness, currentBlendType); + dma_display->drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b); + } + } + + ++time_counter; + ++cycles; + ++fps; + + if (cycles >= 1024) { + time_counter = 0; + cycles = 0; + currentPalette = palettes[random(0,sizeof(palettes)/sizeof(palettes[0]))]; + } + + // print FPS rate every 5 seconds + // Note: this is NOT a matrix refresh rate, it's the number of data frames being drawn to the DMA buffer per second + if (fps_timer + 5000 < millis()){ + Serial.printf_P(PSTR("Effect fps: %d\n"), fps/5); + fps_timer = millis(); + fps = 0; + } +} + + +// FM6126 panel , thanks goes to: +// https://github.com/hzeller/rpi-rgb-led-matrix/issues/746 diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/FM6126A.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/FM6126A.md new file mode 100644 index 0000000..1641c16 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/FM6126A.md @@ -0,0 +1,51 @@ +## The mystery of control registers for FM6126A chips + + +The only available Datasheet for this chips is in Chinese and does not shed a light on what those two control regs are. + +An excellent insight could be found here https://github.com/hzeller/rpi-rgb-led-matrix/issues/746#issuecomment-453860510 + + + +So there are two regs in this chip - **REG1** and **REG2**, +one could be written with 12 clock pulses (and usually called reg12, dunno why :)) +the other one could be written with 13 clock pulses (and usually called reg13, dunno why :)) + + +I've done some measurements on power consumption while toggling bits of **REG1** and it looks that it could provide a fine grained brightness control over the entire matrix with no need for bitbanging over RGB or EO pins. +There are 6 bits (6 to 11) giving an increased brightness (compared to all-zeroes) and 4 bits (2-5) giving decreased brightness!!! +Still unclear if FM6112A brightness control is internally PWMed or current limited, might require some poking with oscilloscope. + +So it seems that the most bright (and hungry for power) value is bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; and not {0,1,1,1,1, 1,1,1,1,1,1, 1,1,1,1,1} as it is usually used. +I'm not sure about bit 1 - it is either not used or I was unable to measure it's influence to brightness/power. + +Giving at least 10 bits of hardware brightness control opens pretty nice options for offloading and simplifying matrix output. Should dig into this more deeper. + +Here are some of the measurements I've took for 2 64x64 panels filled with white color - reg value and corresponding current drain in amps. + + +|REG1 |bit value|Current, amps | +|--|--|--| +|REG1| 0111111 00000| >5 amps| +|REG1| 0100010 00000| 3.890 amp| +|REG1| 0100000 00000| 3.885 amp| +|REG1| 0011110 00000| 3.640 amp| +|REG1| 0011100 00000| 3.620 amp| +|REG1| 0011000 00000| 3.240 amp| +|REG1| 0010010 00000| 2.520 amp| +|REG1| 0010001 00000| 2.518 amp| +|REG1| 0010001 10000| 2.493 amp| +|REG1| 0010000 00000| 2.490 amp| +|REG1| 0010000 11110| 2.214 amp| +|REG1| 0001100 00000| 2.120 amp| +|REG1| 0001000 00000| 1.750 amp| +|REG1| 0000100 00000| 1.375 amp| +|REG1| 0000010 00000| 1.000 amp| +|REG1| **0000000 00000**| 0.995 amp| +|REG1| 0000001 11111| 0.700 amp| +|REG1| 0000000 01111| 0.690 amp| +|REG1| 0000000 10000| 0.690 amp| +|REG1| 0000000 11110| 0.686 amp| + + +/Vortigont/ \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/README.md new file mode 100644 index 0000000..40289cc --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/4_OtherShiftDriverPanel/README.md @@ -0,0 +1,13 @@ +## Ohter driver based LED Matrix Panels ## + +Limited support for other panels exists, but requires this to be passed as a configuration option when using the library. + +These panels require a special reset sequence before they can be used, check your panel chipset if you have issues. Refer to the example. + + +``` + mxconfig.driver = HUB75_I2S_CFG::FM6126A; + mxconfig.driver = HUB75_I2S_CFG::ICN2038S; + mxconfig.driver = HUB75_I2S_CFG::FM6124; + mxconfig.driver = HUB75_I2S_CFG::MBI5124; +``` diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino new file mode 100644 index 0000000..9612244 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino @@ -0,0 +1,268 @@ +/********************************************************************* + * AnimatedGif LED Matrix Panel example where the GIFs are + * stored on a SD card connected to the ESP32 using the + * standard GPIO pins used for SD card acces via. SPI. + * + * Put the gifs into a directory called 'gifs' (case sensitive) on + * a FAT32 formatted SDcard. + ********************************************************************/ +#include "FS.h" +#include "SD.h" +#include "SPI.h" +#include +#include + +/******************************************************************** + * Pin mapping below is for LOLIN D32 (ESP 32) + * + * Default pin mapping used by this library is NOT compatable with the use of the + * ESP32-Arduino 'SD' card library (there is overlap). As such, some of the pins + * used for the HUB75 panel need to be shifted. + * + * 'SD' card library requires GPIO 23, 18 and 19 + * https://github.com/espressif/arduino-esp32/tree/master/libraries/SD + * + */ + +/* + * Connect the SD card to the following pins: + * + * SD Card | ESP32 + * D2 - + * D3 SS + * CMD MOSI + * VSS GND + * VDD 3.3V + * CLK SCK + * VSS GND + * D0 MISO + * D1 - + */ + +/**** SD Card GPIO mappings ****/ +#define SS_PIN 5 +//#define MOSI_PIN 23 +//#define MISO_PIN 19 +//#define CLK_PIN 18 + + +/**** HUB75 GPIO mapping ****/ +// GPIO 34+ are on the ESP32 are input only!! +// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ + +#define A_PIN 33 // remap esp32 library default from 23 to 33 +#define B_PIN 32 // remap esp32 library default from 19 to 32 +#define C_PIN 22 // remap esp32 library defaultfrom 5 to 22 + +//#define R1_PIN 25 // library default for the esp32, unchanged +//#define G1_PIN 26 // library default for the esp32, unchanged +//#define B1_PIN 27 // library default for the esp32, unchanged +//#define R2_PIN 14 // library default for the esp32, unchanged +//#define G2_PIN 12 // library default for the esp32, unchanged +//#define B2_PIN 13 // library default for the esp32, unchanged +//#define D_PIN 17 // library default for the esp32, unchanged +//#define E_PIN -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel. + +//#define LAT_PIN 4 // library default for the esp32, unchanged +//#define OE_PIN 15 // library default for the esp32, unchanged +//#define CLK_PIN 16 // library default for the esp32, unchanged + +/*************************************************************** + * HUB 75 LED DMA Matrix Panel Configuration + **************************************************************/ +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +/**************************************************************/ + +AnimatedGIF gif; +MatrixPanel_I2S_DMA *dma_display = nullptr; + +static int totalFiles = 0; // GIF files count + +static File FSGifFile; // temp gif file holder +static File GifRootFolder; // directory listing + +std::vector GifFiles; // GIF files path + +const int maxGifDuration = 30000; // ms, max GIF duration + +#include "gif_functions.hpp" +#include "sdcard_functions.hpp" + + +/**************************************************************/ +void draw_test_patterns(); +int gifPlay( const char* gifPath ) +{ // 0=infinite + + if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ) { + log_n("Could not open gif %s", gifPath ); + } + + Serial.print("Playing: "); Serial.println(gifPath); + + int frameDelay = 0; // store delay for the last frame + int then = 0; // store overall delay + + while (gif.playFrame(true, &frameDelay)) { + + then += frameDelay; + if( then > maxGifDuration ) { // avoid being trapped in infinite GIF's + //log_w("Broke the GIF loop, max duration exceeded"); + break; + } + } + + gif.close(); + + return then; +} + + +void setup() +{ + Serial.begin(115200); + + // **************************** Setup SD Card access via SPI **************************** + if(!SD.begin(SS_PIN)){ + // bool begin(uint8_t ssPin=SS, SPIClass &spi=SPI, uint32_t frequency=4000000, const char * mountpoint="/sd", uint8_t max_files=5, bool format_if_empty=false); + Serial.println("Card Mount Failed"); + return; + } + uint8_t cardType = SD.cardType(); + + if(cardType == CARD_NONE){ + Serial.println("No SD card attached"); + return; + } + + Serial.print("SD Card Type: "); + if(cardType == CARD_MMC){ + Serial.println("MMC"); + } else if(cardType == CARD_SD){ + Serial.println("SDSC"); + } else if(cardType == CARD_SDHC){ + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + + //listDir(SD, "/", 1, false); + + Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024)); + Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024)); + + + + // **************************** Setup DMA Matrix **************************** + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length + ); + + // Need to remap these HUB75 DMA pins because the SPI SDCard is using them. + // Otherwise the SD Card will not work. + mxconfig.gpio.a = A_PIN; + mxconfig.gpio.b = B_PIN; + mxconfig.gpio.c = C_PIN; + // mxconfig.gpio.d = D_PIN; + + //mxconfig.clkphase = false; + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // Display Setup + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // Allocate memory and start DMA display + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! HUB75 memory allocation failed ***********"); + + dma_display->setBrightness8(128); //0-255 + dma_display->clearScreen(); + + + // **************************** Setup Sketch **************************** + Serial.println("Starting AnimatedGIFs Sketch"); + + // SD CARD STOPS WORKING WITH DMA DISPLAY ENABLED>... + + File root = SD.open("/gifs"); + if(!root){ + Serial.println("Failed to open directory"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(!file.isDirectory()) + { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.println(file.size()); + + std::string filename = "/gifs/" + std::string(file.name()); + Serial.println(filename.c_str()); + + GifFiles.push_back( filename ); + // Serial.println("Adding to gif list:" + String(filename)); + totalFiles++; + + } + file = root.openNextFile(); + } + + file.close(); + Serial.printf("Found %d GIFs to play.", totalFiles); + //totalFiles = getGifInventory("/gifs"); + + + + // This is important - Set the right endianness. + gif.begin(LITTLE_ENDIAN_PIXELS); + +} + +void loop(){ + + // Iterate over a vector using range based for loop + for(auto & elem : GifFiles) + { + gifPlay( elem.c_str() ); + gif.reset(); + delay(500); + } + +} + +void draw_test_patterns() +{ + // fix the screen with green + dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0)); + delay(500); + + // draw a box in yellow + dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0)); + delay(500); + + // draw an 'X' in red + dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0)); + dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0)); + delay(500); + + // draw a blue circle + dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15)); + delay(500); + + // fill a violet circle + dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15)); + delay(500); + delay(1000); + +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/Readme.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/Readme.md new file mode 100644 index 0000000..6f9f06a --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/Readme.md @@ -0,0 +1,15 @@ +# ESP32-HUB75-MatrixPanel-DMA SDCard example + +A very basic example using the 'Animated GIF' library by Larry Bank + the SD / File system library provided for Arduino by Espressif. + +Some default HUB75 pins need to be remapped to accomodate for the SD Card. + +![image](esp32_sdcard.jpg) + +## How to use it? + +1. Format a SD Card with FAT32 file system (default setting) +2. Create a directory called 'gifs' +3. Drop your gifs in there. The resolution of the GIFS must match that of the display. + + diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg new file mode 100644 index 0000000..4a4441d Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gif_functions.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gif_functions.hpp new file mode 100644 index 0000000..7a16a63 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gif_functions.hpp @@ -0,0 +1,132 @@ + +// Code copied from AnimatedGIF examples + +#ifndef M5STACK_SD + // for custom ESP32 builds + #define M5STACK_SD SD +#endif + + +static void * GIFOpenFile(const char *fname, int32_t *pSize) +{ + //log_d("GIFOpenFile( %s )\n", fname ); + FSGifFile = M5STACK_SD.open(fname); + if (FSGifFile) { + *pSize = FSGifFile.size(); + return (void *)&FSGifFile; + } + return NULL; +} + + +static void GIFCloseFile(void *pHandle) +{ + File *f = static_cast(pHandle); + if (f != NULL) + f->close(); +} + + +static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ + int32_t iBytesRead; + iBytesRead = iLen; + File *f = static_cast(pFile->fHandle); + // Note: If you read a file all the way to the last byte, seek() stops working + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around + if (iBytesRead <= 0) + return 0; + iBytesRead = (int32_t)f->read(pBuf, iBytesRead); + pFile->iPos = f->position(); + return iBytesRead; +} + + +static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) +{ + int i = micros(); + File *f = static_cast(pFile->fHandle); + f->seek(iPosition); + pFile->iPos = (int32_t)f->position(); + i = micros() - i; + //log_d("Seek time = %d us\n", i); + return pFile->iPos; +} + + +// Draw a line of image directly on the LCD +void GIFDraw(GIFDRAW *pDraw) +{ + uint8_t *s; + uint16_t *d, *usPalette, usTemp[320]; + int x, y, iWidth; + + iWidth = pDraw->iWidth; + if (iWidth > PANEL_RES_X) + iWidth = PANEL_RES_X; + usPalette = pDraw->pPalette; + y = pDraw->iY + pDraw->y; // current line + + s = pDraw->pPixels; + if (pDraw->ucDisposalMethod == 2) {// restore to background color + for (x=0; xucTransparent) + s[x] = pDraw->ucBackground; + } + pDraw->ucHasTransparency = 0; + } + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; + int x, iCount; + pEnd = s + iWidth; + x = 0; + iCount = 0; // count non-transparent pixels + while(x < iWidth) { + c = ucTransparent-1; + d = usTemp; + while (c != ucTransparent && s < pEnd) { + c = *s++; + if (c == ucTransparent) { // done, stop + s--; // back up to treat it like transparent + } else { // opaque + *d++ = usPalette[c]; + iCount++; + } + } // while looking for opaque pixels + if (iCount) { // any opaque pixels? + for(int xOffset = 0; xOffset < iCount; xOffset++ ){ + dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format + } + x += iCount; + iCount = 0; + } + // no, look for a run of transparent pixels + c = ucTransparent; + while (c == ucTransparent && s < pEnd) { + c = *s++; + if (c == ucTransparent) + iCount++; + else + s--; + } + if (iCount) { + x += iCount; // skip these + iCount = 0; + } + } + } else { + s = pDraw->pPixels; + // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) + for (x=0; xdrawPixel(x, y, usPalette[*s++]); // color 565 + /* + usTemp[x] = usPalette[*s++]; + + for (x=0; xiWidth; x++) { + dma_display->drawPixel(x, y, usTemp[*s++]); // color 565 + } */ + + } +} /* GIFDraw() */ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/cartoon.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/cartoon.gif new file mode 100644 index 0000000..32a0e25 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/cartoon.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif new file mode 100644 index 0000000..0a219a4 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif new file mode 100644 index 0000000..342f8ae Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif new file mode 100644 index 0000000..7925d68 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/parasite1.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/parasite1.gif new file mode 100644 index 0000000..8b8b67a Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/parasite1.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/parasite2.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/parasite2.gif new file mode 100644 index 0000000..60d03c7 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/parasite2.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif new file mode 100644 index 0000000..1d023d9 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp new file mode 100644 index 0000000..51ff5b1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp @@ -0,0 +1,102 @@ +/************************ SD Card Code ************************/ +// As per: https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/examples/SD_Test + + + +void listDir(fs::FS &fs, const char * dirname, uint8_t levels, bool add_to_gif_list = false){ + Serial.printf("Listing directory: %s\n", dirname); + + File root = fs.open(dirname); + if(!root){ + Serial.println("Failed to open directory"); + return; + } + if(!root.isDirectory()){ + Serial.println("Not a directory"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(file.isDirectory()){ + Serial.print(" DIR : "); + Serial.println(file.name()); + if(levels){ + listDir(fs, file.path(), levels -1, false); + } + } else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.println(file.size()); + + if (add_to_gif_list && levels == 0) + { + GifFiles.push_back( std::string(dirname) + file.name() ); + Serial.println("Adding to gif list:" + String(dirname) +"/" + file.name()); + totalFiles++; + } + } + file = root.openNextFile(); + } + + file.close(); +} + +void readFile(fs::FS &fs, const char * path){ + Serial.printf("Reading file: %s\n", path); + + File file = fs.open(path); + if(!file){ + Serial.println("Failed to open file for reading"); + return; + } + + Serial.print("Read from file: "); + while(file.available()){ + Serial.write(file.read()); + } + file.close(); +} + +void testFileIO(fs::FS &fs, const char * path){ + File file = fs.open(path); + static uint8_t buf[512]; + size_t len = 0; + uint32_t start = millis(); + uint32_t end = start; + if(file){ + len = file.size(); + size_t flen = len; + start = millis(); + while(len){ + size_t toRead = len; + if(toRead > 512){ + toRead = 512; + } + file.read(buf, toRead); + len -= toRead; + } + end = millis() - start; + Serial.printf("%u bytes read for %u ms\n", flen, end); + file.close(); + } else { + Serial.println("Failed to open file for reading"); + } + + + file = fs.open(path, FILE_WRITE); + if(!file){ + Serial.println("Failed to open file for writing"); + return; + } + + size_t i; + start = millis(); + for(i=0; i<2048; i++){ + file.write(buf, 512); + } + end = millis() - start; + Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); + file.close(); +} \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/AnimatedGIFPanel_SPIFFS.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/AnimatedGIFPanel_SPIFFS.ino new file mode 100644 index 0000000..9d6d182 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/AnimatedGIFPanel_SPIFFS.ino @@ -0,0 +1,290 @@ +// Example sketch which shows how to display a 64x32 animated GIF image stored in FLASH memory +// on a 64x32 LED matrix +// +// Credits: https://github.com/bitbank2/AnimatedGIF/tree/master/examples/ESP32_LEDMatrix_I2S +// + +/* INSTRUCTIONS + * + * 1. First Run the 'ESP32 Sketch Data Upload Tool' in Arduino from the 'Tools' Menu. + * - If you don't know what this is or see it as an option, then read this: + * https://github.com/me-no-dev/arduino-esp32fs-plugin + * - This tool will upload the contents of the data/ directory in the sketch folder onto + * the ESP32 itself. + * + * 2. You can drop any animated GIF you want in there, but keep it to the resolution of the + * MATRIX you're displaying to. To resize a gif, use this online website: https://ezgif.com/ + * + * 3. Have fun. + */ + +#define FILESYSTEM SPIFFS +#include +#include +#include + +// ---------------------------- + +/* + * Below is an is the 'legacy' way of initialising the MatrixPanel_I2S_DMA class. + * i.e. MATRIX_WIDTH and MATRIX_HEIGHT are modified by compile-time directives. + * By default the library assumes a single 64x32 pixel panel is connected. + * + * Refer to the example '2_PatternPlasma' on the new / correct way to setup this library + * for different resolutions / panel chain lengths within the sketch 'setup()'. + * + */ + +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +//MatrixPanel_I2S_DMA dma_display; +MatrixPanel_I2S_DMA *dma_display = nullptr; + +uint16_t myBLACK = dma_display->color565(0, 0, 0); +uint16_t myWHITE = dma_display->color565(255, 255, 255); +uint16_t myRED = dma_display->color565(255, 0, 0); +uint16_t myGREEN = dma_display->color565(0, 255, 0); +uint16_t myBLUE = dma_display->color565(0, 0, 255); + + +AnimatedGIF gif; +File f; +int x_offset, y_offset; + + + +// Draw a line of image directly on the LED Matrix +void GIFDraw(GIFDRAW *pDraw) +{ + uint8_t *s; + uint16_t *d, *usPalette, usTemp[320]; + int x, y, iWidth; + + iWidth = pDraw->iWidth; + if (iWidth > MATRIX_WIDTH) + iWidth = MATRIX_WIDTH; + + usPalette = pDraw->pPalette; + y = pDraw->iY + pDraw->y; // current line + + s = pDraw->pPixels; + if (pDraw->ucDisposalMethod == 2) // restore to background color + { + for (x=0; xucTransparent) + s[x] = pDraw->ucBackground; + } + pDraw->ucHasTransparency = 0; + } + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) // if transparency used + { + uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; + int x, iCount; + pEnd = s + pDraw->iWidth; + x = 0; + iCount = 0; // count non-transparent pixels + while(x < pDraw->iWidth) + { + c = ucTransparent-1; + d = usTemp; + while (c != ucTransparent && s < pEnd) + { + c = *s++; + if (c == ucTransparent) // done, stop + { + s--; // back up to treat it like transparent + } + else // opaque + { + *d++ = usPalette[c]; + iCount++; + } + } // while looking for opaque pixels + if (iCount) // any opaque pixels? + { + for(int xOffset = 0; xOffset < iCount; xOffset++ ){ + dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format + } + x += iCount; + iCount = 0; + } + // no, look for a run of transparent pixels + c = ucTransparent; + while (c == ucTransparent && s < pEnd) + { + c = *s++; + if (c == ucTransparent) + iCount++; + else + s--; + } + if (iCount) + { + x += iCount; // skip these + iCount = 0; + } + } + } + else // does not have transparency + { + s = pDraw->pPixels; + // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) + for (x=0; xiWidth; x++) + { + dma_display->drawPixel(x, y, usPalette[*s++]); // color 565 + } + } +} /* GIFDraw() */ + + +void * GIFOpenFile(const char *fname, int32_t *pSize) +{ + Serial.print("Playing gif: "); + Serial.println(fname); + f = FILESYSTEM.open(fname); + if (f) + { + *pSize = f.size(); + return (void *)&f; + } + return NULL; +} /* GIFOpenFile() */ + +void GIFCloseFile(void *pHandle) +{ + File *f = static_cast(pHandle); + if (f != NULL) + f->close(); +} /* GIFCloseFile() */ + +int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ + int32_t iBytesRead; + iBytesRead = iLen; + File *f = static_cast(pFile->fHandle); + // Note: If you read a file all the way to the last byte, seek() stops working + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around + if (iBytesRead <= 0) + return 0; + iBytesRead = (int32_t)f->read(pBuf, iBytesRead); + pFile->iPos = f->position(); + return iBytesRead; +} /* GIFReadFile() */ + +int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) +{ + int i = micros(); + File *f = static_cast(pFile->fHandle); + f->seek(iPosition); + pFile->iPos = (int32_t)f->position(); + i = micros() - i; +// Serial.printf("Seek time = %d us\n", i); + return pFile->iPos; +} /* GIFSeekFile() */ + +unsigned long start_tick = 0; + +void ShowGIF(char *name) +{ + start_tick = millis(); + + if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) + { + x_offset = (MATRIX_WIDTH - gif.getCanvasWidth())/2; + if (x_offset < 0) x_offset = 0; + y_offset = (MATRIX_HEIGHT - gif.getCanvasHeight())/2; + if (y_offset < 0) y_offset = 0; + Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); + Serial.flush(); + while (gif.playFrame(true, NULL)) + { + if ( (millis() - start_tick) > 8000) { // we'll get bored after about 8 seconds of the same looping gif + break; + } + } + gif.close(); + } + +} /* ShowGIF() */ + + + +/************************* Arduino Sketch Setup and Loop() *******************************/ +void setup() { + Serial.begin(115200); + delay(1000); + + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length + ); + + // mxconfig.gpio.e = 18; + // mxconfig.clkphase = false; + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // Display Setup + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->setBrightness8(128); //0-255 + dma_display->clearScreen(); + dma_display->fillScreen(myWHITE); + + Serial.println("Starting AnimatedGIFs Sketch"); + + // Start filesystem + Serial.println(" * Loading SPIFFS"); + if(!SPIFFS.begin()){ + Serial.println("SPIFFS Mount Failed"); + } + + dma_display->begin(); + + /* all other pixel drawing functions can only be called after .begin() */ + dma_display->fillScreen(dma_display->color565(0, 0, 0)); + gif.begin(LITTLE_ENDIAN_PIXELS); + +} + +String gifDir = "/gifs"; // play all GIFs in this directory on the SD card +char filePath[256] = { 0 }; +File root, gifFile; + +void loop() +{ + while (1) // run forever + { + + root = FILESYSTEM.open(gifDir); + if (root) + { + gifFile = root.openNextFile(); + while (gifFile) + { + if (!gifFile.isDirectory()) // play it + { + + // C-strings... urghh... + memset(filePath, 0x0, sizeof(filePath)); + strcpy(filePath, gifFile.path()); + + // Show it. + ShowGIF(filePath); + + } + gifFile.close(); + gifFile = root.openNextFile(); + } + root.close(); + } // root + + delay(1000); // pause before restarting + + } // while +} \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/README.md new file mode 100644 index 0000000..63ff899 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/README.md @@ -0,0 +1,13 @@ +## Animated GIF Decoding Example + +### Prerequisites +1. The excellent 'AnimatedGIF' library by Larry Bank needs to be installed: https://github.com/bitbank2/AnimatedGIF + +This is available via the Arduino Library manager, or can be placed in the 'libs' directory with PlatformIO. + +2. The files in the 'data' folder are written to the ESP32's SPIFFS file system. + + +## Credits + +https://github.com/bitbank2/AnimatedGIF diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/cartoon.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/cartoon.gif new file mode 100644 index 0000000..32a0e25 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/cartoon.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/ezgif.com-pacmn.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/ezgif.com-pacmn.gif new file mode 100644 index 0000000..0a219a4 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/ezgif.com-pacmn.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/loading.io-64x32px.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/loading.io-64x32px.gif new file mode 100644 index 0000000..342f8ae Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/loading.io-64x32px.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/matrix-spin.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/matrix-spin.gif new file mode 100644 index 0000000..7925d68 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/matrix-spin.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite1.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite1.gif new file mode 100644 index 0000000..8b8b67a Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite1.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite2.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite2.gif new file mode 100644 index 0000000..60d03c7 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite2.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/shock-gs.gif b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/shock-gs.gif new file mode 100644 index 0000000..1d023d9 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AnimatedGIFPanel_SPIFFS/data/gifs/shock-gs.gif differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Attractor.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Attractor.h new file mode 100644 index 0000000..668ba53 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Attractor.h @@ -0,0 +1,50 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Attractor" in "The Nature of Code" by Daniel Shiffman: http://natureofcode.com/ + * Copyright (c) 2014 Daniel Shiffman + * http://www.shiffman.net + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "Vector.h" + +class Attractor { +public: + float mass; // Mass, tied to size + float G; // Gravitational Constant + PVector location; // Location + + Attractor() { + location = PVector(MATRIX_CENTRE_X, MATRIX_CENTRE_Y); + mass = 10; + G = .5; + } + + PVector attract(Boid m) { + PVector force = location - m.location; // Calculate direction of force + float d = force.mag(); // Distance between objects + d = constrain(d, 5.0, 32.0); // Limiting the distance to eliminate "extreme" results for very close or very far objects + force.normalize(); // Normalize vector (distance doesn't matter here, we just want this vector for direction) + float strength = (G * mass * m.mass) / (d * d); // Calculate gravitational force magnitude + force *= strength; // Get force vector --> magnitude * direction + return force; + } +}; diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/AuroraDemo.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/AuroraDemo.ino new file mode 100644 index 0000000..14b61be --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/AuroraDemo.ino @@ -0,0 +1,150 @@ +#include + +/*--------------------- MATRIX GPIO CONFIG -------------------------*/ +#define R1_PIN 25 +#define G1_PIN 26 +#define B1_PIN 27 +#define R2_PIN 14 +#define G2_PIN 12 +#define B2_PIN 13 +#define A_PIN 23 +#define B_PIN 19 // Changed from library default +#define C_PIN 5 +#define D_PIN 17 +#define E_PIN -1 +#define LAT_PIN 4 +#define OE_PIN 15 +#define CLK_PIN 16 + + +/*--------------------- MATRIX PANEL CONFIG -------------------------*/ +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +/* +//Another way of creating config structure +//Custom pin mapping for all pins +HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; +HUB75_I2S_CFG mxconfig( + 64, // width + 64, // height + 4, // chain length + _pins, // pin mapping + HUB75_I2S_CFG::FM6126A // driver chip +); + +*/ +MatrixPanel_I2S_DMA *dma_display = nullptr; + +// Module configuration +HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length +); + + +//mxconfig.gpio.e = -1; // Assign a pin if you have a 64x64 panel +//mxconfig.clkphase = false; // Change this if you have issues with ghosting. +//mxconfig.driver = HUB75_I2S_CFG::FM6126A; // Change this according to your pane. + + + +#include + +#include "Effects.h" +Effects effects; + +#include "Drawable.h" +#include "Playlist.h" +//#include "Geometry.h" + +#include "Patterns.h" +Patterns patterns; + +/* -------------------------- Some variables -------------------------- */ +unsigned long fps = 0, fps_timer; // fps (this is NOT a matrix refresh rate!) +unsigned int default_fps = 30, pattern_fps = 30; // default fps limit (this is not a matrix refresh counter!) +unsigned long ms_animation_max_duration = 20000; // 20 seconds +unsigned long last_frame=0, ms_previous=0; + +void setup() +{ + /************** SERIAL **************/ + Serial.begin(115200); + delay(250); + + /************** DISPLAY **************/ + Serial.println("...Starting Display"); + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->setBrightness8(90); //0-255 + + dma_display->fillScreenRGB888(128,0,0); + delay(1000); + dma_display->fillScreenRGB888(0,0,128); + delay(1000); + dma_display->clearScreen(); + delay(1000); + Serial.println("**************** Starting Aurora Effects Demo ****************"); + + + // setup the effects generator + effects.Setup(); + + delay(500); + Serial.println("Effects being loaded: "); + listPatterns(); + + + patterns.moveRandom(1); // start from a random pattern + + Serial.print("Starting with pattern: "); + Serial.println(patterns.getCurrentPatternName()); + patterns.start(); + ms_previous = millis(); + fps_timer = millis(); +} + +void loop() +{ + // menu.run(mainMenuItems, mainMenuItemCount); + + if ( (millis() - ms_previous) > ms_animation_max_duration ) + { + patterns.stop(); + patterns.moveRandom(1); + //patterns.move(1); + patterns.start(); + + Serial.print("Changing pattern to: "); + Serial.println(patterns.getCurrentPatternName()); + + ms_previous = millis(); + + // Select a random palette as well + //effects.RandomPalette(); + } + + if ( 1000 / pattern_fps + last_frame < millis()){ + last_frame = millis(); + pattern_fps = patterns.drawFrame(); + if (!pattern_fps) + pattern_fps = default_fps; + + ++fps; + } + + if (fps_timer + 1000 < millis()){ + Serial.printf_P(PSTR("Effect fps: %ld\n"), fps); + fps_timer = millis(); + fps = 0; + } + +} + + +void listPatterns() { + patterns.listPatterns(); +} \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Boid.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Boid.h new file mode 100644 index 0000000..61a1758 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Boid.h @@ -0,0 +1,326 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Flocking" in "The Nature of Code" by Daniel Shiffman: http://natureofcode.com/ + * Copyright (c) 2014 Daniel Shiffman + * http://www.shiffman.net + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Flocking +// Daniel Shiffman +// The Nature of Code, Spring 2009 + +// Boid class +// Methods for Separation, Cohesion, Alignment added + +class Boid { + public: + + PVector location; + PVector velocity; + PVector acceleration; + float maxforce; // Maximum steering force + float maxspeed; // Maximum speed + + float desiredseparation = 4; + float neighbordist = 8; + byte colorIndex = 0; + float mass; + + boolean enabled = true; + + Boid() {} + + Boid(float x, float y) { + acceleration = PVector(0, 0); + velocity = PVector(randomf(), randomf()); + location = PVector(x, y); + maxspeed = 1.5; + maxforce = 0.05; + } + + static float randomf() { + return mapfloat(random(0, 255), 0, 255, -.5, .5); + } + + static float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } + + void run(Boid boids [], uint8_t boidCount) { + flock(boids, boidCount); + update(); + // wrapAroundBorders(); + // render(); + } + + // Method to update location + void update() { + // Update velocity + velocity += acceleration; + // Limit speed + velocity.limit(maxspeed); + location += velocity; + // Reset acceleration to 0 each cycle + acceleration *= 0; + } + + void applyForce(PVector force) { + // We could add mass here if we want A = F / M + acceleration += force; + } + + void repelForce(PVector obstacle, float radius) { + //Force that drives boid away from obstacle. + + PVector futPos = location + velocity; //Calculate future position for more effective behavior. + PVector dist = obstacle - futPos; + float d = dist.mag(); + + if (d <= radius) { + PVector repelVec = location - obstacle; + repelVec.normalize(); + if (d != 0) { //Don't divide by zero. + // float scale = 1.0 / d; //The closer to the obstacle, the stronger the force. + repelVec.normalize(); + repelVec *= (maxforce * 7); + if (repelVec.mag() < 0) { //Don't let the boids turn around to avoid the obstacle. + repelVec.y = 0; + } + } + applyForce(repelVec); + } + } + + // We accumulate a new acceleration each time based on three rules + void flock(Boid boids [], uint8_t boidCount) { + PVector sep = separate(boids, boidCount); // Separation + PVector ali = align(boids, boidCount); // Alignment + PVector coh = cohesion(boids, boidCount); // Cohesion + // Arbitrarily weight these forces + sep *= 1.5; + ali *= 1.0; + coh *= 1.0; + // Add the force vectors to acceleration + applyForce(sep); + applyForce(ali); + applyForce(coh); + } + + // Separation + // Method checks for nearby boids and steers away + PVector separate(Boid boids [], uint8_t boidCount) { + PVector steer = PVector(0, 0); + int count = 0; + // For every boid in the system, check if it's too close + for (int i = 0; i < boidCount; i++) { + Boid other = boids[i]; + if (!other.enabled) + continue; + float d = location.dist(other.location); + // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) + if ((d > 0) && (d < desiredseparation)) { + // Calculate vector pointing away from neighbor + PVector diff = location - other.location; + diff.normalize(); + diff /= d; // Weight by distance + steer += diff; + count++; // Keep track of how many + } + } + // Average -- divide by how many + if (count > 0) { + steer /= (float) count; + } + + // As long as the vector is greater than 0 + if (steer.mag() > 0) { + // Implement Reynolds: Steering = Desired - Velocity + steer.normalize(); + steer *= maxspeed; + steer -= velocity; + steer.limit(maxforce); + } + return steer; + } + + // Alignment + // For every nearby boid in the system, calculate the average velocity + PVector align(Boid boids [], uint8_t boidCount) { + PVector sum = PVector(0, 0); + int count = 0; + for (int i = 0; i < boidCount; i++) { + Boid other = boids[i]; + if (!other.enabled) + continue; + float d = location.dist(other.location); + if ((d > 0) && (d < neighbordist)) { + sum += other.velocity; + count++; + } + } + if (count > 0) { + sum /= (float) count; + sum.normalize(); + sum *= maxspeed; + PVector steer = sum - velocity; + steer.limit(maxforce); + return steer; + } + else { + return PVector(0, 0); + } + } + + // Cohesion + // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location + PVector cohesion(Boid boids [], uint8_t boidCount) { + PVector sum = PVector(0, 0); // Start with empty vector to accumulate all locations + int count = 0; + for (int i = 0; i < boidCount; i++) { + Boid other = boids[i]; + if (!other.enabled) + continue; + float d = location.dist(other.location); + if ((d > 0) && (d < neighbordist)) { + sum += other.location; // Add location + count++; + } + } + if (count > 0) { + sum /= count; + return seek(sum); // Steer towards the location + } + else { + return PVector(0, 0); + } + } + + // A method that calculates and applies a steering force towards a target + // STEER = DESIRED MINUS VELOCITY + PVector seek(PVector target) { + PVector desired = target - location; // A vector pointing from the location to the target + // Normalize desired and scale to maximum speed + desired.normalize(); + desired *= maxspeed; + // Steering = Desired minus Velocity + PVector steer = desired - velocity; + steer.limit(maxforce); // Limit to maximum steering force + return steer; + } + + // A method that calculates a steering force towards a target + // STEER = DESIRED MINUS VELOCITY + void arrive(PVector target) { + PVector desired = target - location; // A vector pointing from the location to the target + float d = desired.mag(); + // Normalize desired and scale with arbitrary damping within 100 pixels + desired.normalize(); + if (d < 4) { + float m = map(d, 0, 100, 0, maxspeed); + desired *= m; + } + else { + desired *= maxspeed; + } + + // Steering = Desired minus Velocity + PVector steer = desired - velocity; + steer.limit(maxforce); // Limit to maximum steering force + applyForce(steer); + //Serial.println(d); + } + + void wrapAroundBorders() { + if (location.x < 0) location.x = MATRIX_WIDTH - 1; + if (location.y < 0) location.y = MATRIX_HEIGHT - 1; + if (location.x >= MATRIX_WIDTH) location.x = 0; + if (location.y >= MATRIX_HEIGHT) location.y = 0; + } + + void avoidBorders() { + PVector desired = velocity; + + if (location.x < 8) desired = PVector(maxspeed, velocity.y); + if (location.x >= MATRIX_WIDTH - 8) desired = PVector(-maxspeed, velocity.y); + if (location.y < 8) desired = PVector(velocity.x, maxspeed); + if (location.y >= MATRIX_HEIGHT - 8) desired = PVector(velocity.x, -maxspeed); + + if (desired != velocity) { + PVector steer = desired - velocity; + steer.limit(maxforce); + applyForce(steer); + } + + if (location.x < 0) location.x = 0; + if (location.y < 0) location.y = 0; + if (location.x >= MATRIX_WIDTH) location.x = MATRIX_WIDTH - 1; + if (location.y >= MATRIX_HEIGHT) location.y = MATRIX_HEIGHT - 1; + } + + bool bounceOffBorders(float bounce) { + bool bounced = false; + + if (location.x >= MATRIX_WIDTH) { + location.x = MATRIX_WIDTH - 1; + velocity.x *= -bounce; + bounced = true; + } + else if (location.x < 0) { + location.x = 0; + velocity.x *= -bounce; + bounced = true; + } + + if (location.y >= MATRIX_HEIGHT) { + location.y = MATRIX_HEIGHT - 1; + velocity.y *= -bounce; + bounced = true; + } + else if (location.y < 0) { + location.y = 0; + velocity.y *= -bounce; + bounced = true; + } + + return bounced; + } + + void render() { + //// Draw a triangle rotated in the direction of velocity + //float theta = velocity.heading2D() + radians(90); + //fill(175); + //stroke(0); + //pushMatrix(); + //translate(location.x,location.y); + //rotate(theta); + //beginShape(TRIANGLES); + //vertex(0, -r*2); + //vertex(-r, r*2); + //vertex(r, r*2); + //endShape(); + //popMatrix(); + //dma_display->drawBackgroundPixelRGB888(location.x, location.y, CRGB::Blue); + } +}; + +static const uint8_t AVAILABLE_BOID_COUNT = 40; +Boid boids[AVAILABLE_BOID_COUNT]; diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Drawable.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Drawable.h new file mode 100644 index 0000000..56b3eb2 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Drawable.h @@ -0,0 +1,55 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Drawable_H +#define Drawable_H + +class Drawable{ +public: + char* name; + + virtual bool isRunnable() { + return false; + } + + virtual bool isPlaylist() { + return false; + } + + // a single frame should be drawn as fast as possible, without any delay or blocking + // return how many millisecond delay is requested before the next call to drawFrame() + virtual unsigned int drawFrame() { + dma_display->fillScreen(0); + //backgroundLayer.fillScreen({ 0, 0, 0 }); + return 0; + }; + + virtual void printTesting() + { + Serial.println("Testing..."); + } + + virtual void start() {}; + virtual void stop() {}; +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Effects.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Effects.h new file mode 100644 index 0000000..1b50329 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Effects.h @@ -0,0 +1,848 @@ + +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Funky Clouds" by Stefan Petrick: https://gist.github.com/anonymous/876f908333cd95315c35 + * Portions of this code are adapted from "NoiseSmearing" by Stefan Petrick: https://gist.github.com/StefanPetrick/9ee2f677dbff64e3ba7a + * Copyright (c) 2014 Stefan Petrick + * http://www.stefan-petrick.de/wordpress_beta + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Effects_H +#define Effects_H + +/* ---------------------------- GLOBAL CONSTANTS ----------------------------- */ + +const int MATRIX_CENTER_X = MATRIX_WIDTH / 2; +const int MATRIX_CENTER_Y = MATRIX_HEIGHT / 2; +// US vs GB, huh? :) +//const byte MATRIX_CENTRE_X = MATRIX_CENTER_X - 1; +//const byte MATRIX_CENTRE_Y = MATRIX_CENTER_Y - 1; +#define MATRIX_CENTRE_X MATRIX_CENTER_X +#define MATRIX_CENTRE_Y MATRIX_CENTER_Y + + +const uint16_t NUM_LEDS = (MATRIX_WIDTH * MATRIX_HEIGHT) + 1; // one led spare to capture out of bounds + +// forward declaration +uint16_t XY16( uint16_t x, uint16_t y); + +/* Convert x,y co-ordinate to flat array index. + * x and y positions start from 0, so must not be >= 'real' panel width or height + * (i.e. 64 pixels or 32 pixels.). Max value: MATRIX_WIDTH-1 etc. + * Ugh... uint8_t - really??? this weak method can't cope with 256+ pixel matrices :( + */ +uint16_t XY( uint8_t x, uint8_t y) +{ + return XY16(x, y); +} + +/** + * The one for 256+ matrices + * otherwise this: + * for (uint8_t i = 0; i < MATRIX_WIDTH; i++) {} + * turns into an infinite loop + */ +uint16_t XY16( uint16_t x, uint16_t y) +{ + if( x >= MATRIX_WIDTH) return 0; + if( y >= MATRIX_HEIGHT) return 0; + + return (y * MATRIX_WIDTH) + x + 1; // everything offset by one to compute out of bounds stuff - never displayed by ShowFrame() +} + + +uint8_t beatcos8(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0) +{ + uint8_t beat = beat8(beats_per_minute, timebase); + uint8_t beatcos = cos8(beat + phase_offset); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatcos, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} + +uint8_t mapsin8(uint8_t theta, uint8_t lowest = 0, uint8_t highest = 255) { + uint8_t beatsin = sin8(theta); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatsin, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} + +uint8_t mapcos8(uint8_t theta, uint8_t lowest = 0, uint8_t highest = 255) { + uint8_t beatcos = cos8(theta); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatcos, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} + +// Array of temperature readings at each simulation cell +//byte heat[NUM_LEDS]; // none of the currently enabled effects uses this + +uint32_t noise_x; +uint32_t noise_y; +uint32_t noise_z; +uint32_t noise_scale_x; +uint32_t noise_scale_y; + +//uint8_t noise[MATRIX_WIDTH][MATRIX_HEIGHT]; +uint8_t **noise = nullptr; // we will allocate mem later +uint8_t noisesmoothing; + +class Effects { +public: + CRGB *leds; + //CRGB leds[NUM_LEDS]; + //CRGB leds2[NUM_LEDS]; // Faptastic: getting rid of this and any dependant effects or algos. to save memory 24*64*32 bytes of ram (50k). + + Effects(){ + // we do dynamic allocation for leds buffer, otherwise esp32 toolchain can't link static arrays of such a big size for 256+ matrices + leds = (CRGB *)malloc(NUM_LEDS * sizeof(CRGB)); + + // allocate mem for noise effect + // (there should be some guards for malloc errors eventually) + noise = (uint8_t **)malloc(MATRIX_WIDTH * sizeof(uint8_t *)); + for (int i = 0; i < MATRIX_WIDTH; ++i) { + noise[i] = (uint8_t *)malloc(MATRIX_HEIGHT * sizeof(uint8_t)); + } + + ClearFrame(); + //dma_display->clearScreen(); + } + ~Effects(){ + free(leds); + for (int i = 0; i < MATRIX_WIDTH; ++i) { + free(noise[i]); + } + free(noise); + } + + /* The only 'framebuffer' we have is what is contained in the leds and leds2 variables. + * We don't store what the color a particular pixel might be, other than when it's turned + * into raw electrical signal output gobbly-gook (i.e. the DMA matrix buffer), but this * is not reversible. + * + * As such, any time these effects want to write a pixel color, we first have to update + * the leds or leds2 array, and THEN write it to the RGB panel. This enables us to 'look up' the array to see what a pixel color was previously, each drawFrame(). + */ + void drawBackgroundFastLEDPixelCRGB(int16_t x, int16_t y, CRGB color) + { + leds[XY(x, y)] = color; + //dma_display->drawPixelRGB888(x, y, color.r, color.g, color.b); + } + + // write one pixel with the specified color from the current palette to coordinates + void Pixel(int x, int y, uint8_t colorIndex) { + leds[XY(x, y)] = ColorFromCurrentPalette(colorIndex); + //dma_display->drawPixelRGB888(x, y, temp.r, temp.g, temp.b); // now draw it? + } + + void PrepareFrame() { + // leds = (CRGB*) backgroundLayer.backBuffer(); + } + + void ShowFrame() { + //#if (FASTLED_VERSION >= 3001000) + // nblendPaletteTowardPalette(currentPalette, targetPalette, 24); + //#else + currentPalette = targetPalette; + //#endif + + // backgroundLayer.swapBuffers(); + // leds = (CRGB*) backgroundLayer.backBuffer(); + // LEDS.countFPS(); + + for (int y=0; ydrawPixelRGB888( x, y, leds[_pixel].r, leds[_pixel].g, leds[_pixel].b); + } // end loop to copy fast led to the dma matrix + } + } + + // scale the brightness of the screenbuffer down + void DimAll(byte value) + { + for (int i = 0; i < NUM_LEDS; i++) + { + leds[i].nscale8(value); + } + } + + void ClearFrame() + { + memset(leds, 0x00, NUM_LEDS * sizeof(CRGB)); // flush + } + + + +/* + void CircleStream(uint8_t value) { + DimAll(value); ShowFrame(); + + for (uint8_t offset = 0; offset < MATRIX_CENTER_X; offset++) { + boolean hasprev = false; + uint16_t prevxy = 0; + + for (uint8_t theta = 0; theta < 255; theta++) { + uint8_t x = mapcos8(theta, offset, (MATRIX_WIDTH - 1) - offset); + uint8_t y = mapsin8(theta, offset, (MATRIX_HEIGHT - 1) - offset); + + uint16_t xy = XY(x, y); + + if (hasprev) { + leds[prevxy] += leds[xy]; + } + + prevxy = xy; + hasprev = true; + } + } + + for (uint8_t x = 0; x < MATRIX_WIDTH; x++) { + for (uint8_t y = 0; y < MATRIX_HEIGHT; y++) { + uint16_t xy = XY(x, y); + leds[xy] = leds2[xy]; + leds[xy].nscale8(value); + leds2[xy].nscale8(value); + } + } + } +*/ + + // palettes + static const int paletteCount = 10; + int paletteIndex = -1; + TBlendType currentBlendType = LINEARBLEND; + CRGBPalette16 currentPalette; + CRGBPalette16 targetPalette; + char* currentPaletteName; + + static const int HeatColorsPaletteIndex = 6; + static const int RandomPaletteIndex = 9; + + void Setup() { + currentPalette = RainbowColors_p; + loadPalette(0); + NoiseVariablesSetup(); + } + + void CyclePalette(int offset = 1) { + loadPalette(paletteIndex + offset); + } + + void RandomPalette() { + loadPalette(RandomPaletteIndex); + } + + void loadPalette(int index) { + paletteIndex = index; + + if (paletteIndex >= paletteCount) + paletteIndex = 0; + else if (paletteIndex < 0) + paletteIndex = paletteCount - 1; + + switch (paletteIndex) { + case 0: + targetPalette = RainbowColors_p; + currentPaletteName = (char *)"Rainbow"; + break; + //case 1: + // targetPalette = RainbowStripeColors_p; + // currentPaletteName = (char *)"RainbowStripe"; + // break; + case 1: + targetPalette = OceanColors_p; + currentPaletteName = (char *)"Ocean"; + break; + case 2: + targetPalette = CloudColors_p; + currentPaletteName = (char *)"Cloud"; + break; + case 3: + targetPalette = ForestColors_p; + currentPaletteName = (char *)"Forest"; + break; + case 4: + targetPalette = PartyColors_p; + currentPaletteName = (char *)"Party"; + break; + case 5: + setupGrayscalePalette(); + currentPaletteName = (char *)"Grey"; + break; + case HeatColorsPaletteIndex: + targetPalette = HeatColors_p; + currentPaletteName = (char *)"Heat"; + break; + case 7: + targetPalette = LavaColors_p; + currentPaletteName = (char *)"Lava"; + break; + case 8: + setupIcePalette(); + currentPaletteName = (char *)"Ice"; + break; + case RandomPaletteIndex: + loadPalette(random(0, paletteCount - 1)); + paletteIndex = RandomPaletteIndex; + currentPaletteName = (char *)"Random"; + break; + } + } + + void setPalette(String paletteName) { + if (paletteName == "Rainbow") + loadPalette(0); + //else if (paletteName == "RainbowStripe") + // loadPalette(1); + else if (paletteName == "Ocean") + loadPalette(1); + else if (paletteName == "Cloud") + loadPalette(2); + else if (paletteName == "Forest") + loadPalette(3); + else if (paletteName == "Party") + loadPalette(4); + else if (paletteName == "Grayscale") + loadPalette(5); + else if (paletteName == "Heat") + loadPalette(6); + else if (paletteName == "Lava") + loadPalette(7); + else if (paletteName == "Ice") + loadPalette(8); + else if (paletteName == "Random") + RandomPalette(); + } + + void listPalettes() { + Serial.println(F("{")); + Serial.print(F(" \"count\": ")); + Serial.print(paletteCount); + Serial.println(","); + Serial.println(F(" \"results\": [")); + + String paletteNames [] = { + "Rainbow", + // "RainbowStripe", + "Ocean", + "Cloud", + "Forest", + "Party", + "Grayscale", + "Heat", + "Lava", + "Ice", + "Random" + }; + + for (int i = 0; i < paletteCount; i++) { + Serial.print(F(" \"")); + Serial.print(paletteNames[i]); + if (i == paletteCount - 1) + Serial.println(F("\"")); + else + Serial.println(F("\",")); + } + + Serial.println(" ]"); + Serial.println("}"); + } + + void setupGrayscalePalette() { + targetPalette = CRGBPalette16(CRGB::Black, CRGB::White); + } + + void setupIcePalette() { + targetPalette = CRGBPalette16(CRGB::Black, CRGB::Blue, CRGB::Aqua, CRGB::White); + } + + // Oscillators and Emitters + + // the oscillators: linear ramps 0-255 + byte osci[6]; + + // sin8(osci) swinging between 0 to MATRIX_WIDTH - 1 + byte p[6]; + + // set the speeds (and by that ratios) of the oscillators here + void MoveOscillators() { + osci[0] = osci[0] + 5; + osci[1] = osci[1] + 2; + osci[2] = osci[2] + 3; + osci[3] = osci[3] + 4; + osci[4] = osci[4] + 1; + if (osci[4] % 2 == 0) + osci[5] = osci[5] + 1; // .5 + for (int i = 0; i < 4; i++) { + p[i] = map8(sin8(osci[i]), 0, MATRIX_WIDTH - 1); //why? to keep the result in the range of 0-MATRIX_WIDTH (matrix size) + } + } + + + // All the caleidoscope functions work directly within the screenbuffer (leds array). + // Draw whatever you like in the area x(0-15) and y (0-15) and then copy it arround. + + // rotates the first 16x16 quadrant 3 times onto a 32x32 (+90 degrees rotation for each one) + void Caleidoscope1() { + for (int x = 0; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < MATRIX_CENTER_Y; y++) { + leds[XY16(MATRIX_WIDTH - 1 - x, y)] = leds[XY16(x, y)]; + leds[XY16(MATRIX_WIDTH - 1 - x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(x, y)]; + leds[XY16(x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(x, y)]; + } + } + } + + + // mirror the first 16x16 quadrant 3 times onto a 32x32 + void Caleidoscope2() { + for (int x = 0; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < MATRIX_CENTER_Y; y++) { + leds[XY16(MATRIX_WIDTH - 1 - x, y)] = leds[XY16(y, x)]; + leds[XY16(x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(y, x)]; + leds[XY16(MATRIX_WIDTH - 1 - x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(x, y)]; + } + } + } + + // copy one diagonal triangle into the other one within a 16x16 + void Caleidoscope3() { + for (int x = 0; x <= MATRIX_CENTRE_X && x < MATRIX_HEIGHT; x++) { + for (int y = 0; y <= x && y= 0; y--) { + leds[XY16(x, y)] = leds[XY16(y, x)]; + } + } + } + + void Caleidoscope6() { + for (int x = 1; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 7)] = leds[XY16(x, 0)]; + } //a + for (int x = 2; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 6)] = leds[XY16(x, 1)]; + } //b + for (int x = 3; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 5)] = leds[XY16(x, 2)]; + } //c + for (int x = 4; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 4)] = leds[XY16(x, 3)]; + } //d + for (int x = 5; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 3)] = leds[XY16(x, 4)]; + } //e + for (int x = 6; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 2)] = leds[XY16(x, 5)]; + } //f + for (int x = 7; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 1)] = leds[XY16(x, 6)]; + } //g + } + + // create a square twister to the left or counter-clockwise + // x and y for center, r for radius + void SpiralStream(int x, int y, int r, byte dimm) { + for (int d = r; d >= 0; d--) { // from the outside to the inside + for (int i = x - d; i <= x + d; i++) { + leds[XY16(i, y - d)] += leds[XY16(i + 1, y - d)]; // lowest row to the right + leds[XY16(i, y - d)].nscale8(dimm); + } + for (int i = y - d; i <= y + d; i++) { + leds[XY16(x + d, i)] += leds[XY16(x + d, i + 1)]; // right column up + leds[XY16(x + d, i)].nscale8(dimm); + } + for (int i = x + d; i >= x - d; i--) { + leds[XY16(i, y + d)] += leds[XY16(i - 1, y + d)]; // upper row to the left + leds[XY16(i, y + d)].nscale8(dimm); + } + for (int i = y + d; i >= y - d; i--) { + leds[XY16(x - d, i)] += leds[XY16(x - d, i - 1)]; // left column down + leds[XY16(x - d, i)].nscale8(dimm); + } + } + } + + // expand everything within a circle + void Expand(int centerX, int centerY, int radius, byte dimm) { + if (radius == 0) + return; + + int currentRadius = radius; + + while (currentRadius > 0) { + int a = radius, b = 0; + int radiusError = 1 - a; + + int nextRadius = currentRadius - 1; + int nextA = nextRadius - 1, nextB = 0; + int nextRadiusError = 1 - nextA; + + while (a >= b) + { + // move them out one pixel on the radius + leds[XY16(a + centerX, b + centerY)] = leds[XY16(nextA + centerX, nextB + centerY)]; + leds[XY16(b + centerX, a + centerY)] = leds[XY16(nextB + centerX, nextA + centerY)]; + leds[XY16(-a + centerX, b + centerY)] = leds[XY16(-nextA + centerX, nextB + centerY)]; + leds[XY16(-b + centerX, a + centerY)] = leds[XY16(-nextB + centerX, nextA + centerY)]; + leds[XY16(-a + centerX, -b + centerY)] = leds[XY16(-nextA + centerX, -nextB + centerY)]; + leds[XY16(-b + centerX, -a + centerY)] = leds[XY16(-nextB + centerX, -nextA + centerY)]; + leds[XY16(a + centerX, -b + centerY)] = leds[XY16(nextA + centerX, -nextB + centerY)]; + leds[XY16(b + centerX, -a + centerY)] = leds[XY16(nextB + centerX, -nextA + centerY)]; + + // dim them + leds[XY16(a + centerX, b + centerY)].nscale8(dimm); + leds[XY16(b + centerX, a + centerY)].nscale8(dimm); + leds[XY16(-a + centerX, b + centerY)].nscale8(dimm); + leds[XY16(-b + centerX, a + centerY)].nscale8(dimm); + leds[XY16(-a + centerX, -b + centerY)].nscale8(dimm); + leds[XY16(-b + centerX, -a + centerY)].nscale8(dimm); + leds[XY16(a + centerX, -b + centerY)].nscale8(dimm); + leds[XY16(b + centerX, -a + centerY)].nscale8(dimm); + + b++; + if (radiusError < 0) + radiusError += 2 * b + 1; + else + { + a--; + radiusError += 2 * (b - a + 1); + } + + nextB++; + if (nextRadiusError < 0) + nextRadiusError += 2 * nextB + 1; + else + { + nextA--; + nextRadiusError += 2 * (nextB - nextA + 1); + } + } + + currentRadius--; + } + } + + // give it a linear tail to the right + void StreamRight(byte scale, int fromX = 0, int toX = MATRIX_WIDTH, int fromY = 0, int toY = MATRIX_HEIGHT) + { + for (int x = fromX + 1; x < toX; x++) { + for (int y = fromY; y < toY; y++) { + leds[XY16(x, y)] += leds[XY16(x - 1, y)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int y = fromY; y < toY; y++) + leds[XY16(0, y)].nscale8(scale); + } + + // give it a linear tail to the left + void StreamLeft(byte scale, int fromX = MATRIX_WIDTH, int toX = 0, int fromY = 0, int toY = MATRIX_HEIGHT) + { + for (int x = toX; x < fromX; x++) { + for (int y = fromY; y < toY; y++) { + leds[XY16(x, y)] += leds[XY16(x + 1, y)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int y = fromY; y < toY; y++) + leds[XY16(0, y)].nscale8(scale); + } + + // give it a linear tail downwards + void StreamDown(byte scale) + { + for (int x = 0; x < MATRIX_WIDTH; x++) { + for (int y = 1; y < MATRIX_HEIGHT; y++) { + leds[XY16(x, y)] += leds[XY16(x, y - 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int x = 0; x < MATRIX_WIDTH; x++) + leds[XY16(x, 0)].nscale8(scale); + } + + // give it a linear tail upwards + void StreamUp(byte scale) + { + for (int x = 0; x < MATRIX_WIDTH; x++) { + for (int y = MATRIX_HEIGHT - 2; y >= 0; y--) { + leds[XY16(x, y)] += leds[XY16(x, y + 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int x = 0; x < MATRIX_WIDTH; x++) + leds[XY16(x, MATRIX_HEIGHT - 1)].nscale8(scale); + } + + // give it a linear tail up and to the left + void StreamUpAndLeft(byte scale) + { + for (int x = 0; x < MATRIX_WIDTH - 1; x++) { + for (int y = MATRIX_HEIGHT - 2; y >= 0; y--) { + leds[XY16(x, y)] += leds[XY16(x + 1, y + 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int x = 0; x < MATRIX_WIDTH; x++) + leds[XY16(x, MATRIX_HEIGHT - 1)].nscale8(scale); + for (int y = 0; y < MATRIX_HEIGHT; y++) + leds[XY16(MATRIX_WIDTH - 1, y)].nscale8(scale); + } + + // give it a linear tail up and to the right + void StreamUpAndRight(byte scale) + { + for (int x = 0; x < MATRIX_WIDTH - 1; x++) { + for (int y = MATRIX_HEIGHT - 2; y >= 0; y--) { + leds[XY16(x + 1, y)] += leds[XY16(x, y + 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + // fade the bottom row + for (int x = 0; x < MATRIX_WIDTH; x++) + leds[XY16(x, MATRIX_HEIGHT - 1)].nscale8(scale); + + // fade the right column + for (int y = 0; y < MATRIX_HEIGHT; y++) + leds[XY16(MATRIX_WIDTH - 1, y)].nscale8(scale); + } + + // just move everything one line down + void MoveDown() { + for (int y = MATRIX_HEIGHT - 1; y > 0; y--) { + for (int x = 0; x < MATRIX_WIDTH; x++) { + leds[XY16(x, y)] = leds[XY16(x, y - 1)]; + } + } + } + + // just move everything one line down + void VerticalMoveFrom(int start, int end) { + for (int y = end; y > start; y--) { + for (int x = 0; x < MATRIX_WIDTH; x++) { + leds[XY16(x, y)] = leds[XY16(x, y - 1)]; + } + } + } + + // copy the rectangle defined with 2 points x0, y0, x1, y1 + // to the rectangle beginning at x2, x3 + void Copy(byte x0, byte y0, byte x1, byte y1, byte x2, byte y2) { + for (int y = y0; y < y1 + 1; y++) { + for (int x = x0; x < x1 + 1; x++) { + leds[XY16(x + x2 - x0, y + y2 - y0)] = leds[XY16(x, y)]; + } + } + } + + // rotate + copy triangle (MATRIX_CENTER_X*MATRIX_CENTER_X) + void RotateTriangle() { + for (int x = 1; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < x; y++) { + leds[XY16(x, 7 - y)] = leds[XY16(7 - x, y)]; + } + } + } + + // mirror + copy triangle (MATRIX_CENTER_X*MATRIX_CENTER_X) + void MirrorTriangle() { + for (int x = 1; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < x; y++) { + leds[XY16(7 - y, x)] = leds[XY16(7 - x, y)]; + } + } + } + + // draw static rainbow triangle pattern (MATRIX_CENTER_XxWIDTH / 2) + // (just for debugging) + void RainbowTriangle() { + for (int i = 0; i < MATRIX_CENTER_X; i++) { + for (int j = 0; j <= i; j++) { + Pixel(7 - i, j, i * j * 4); + } + } + } + + void BresenhamLine(int x0, int y0, int x1, int y1, byte colorIndex) + { + BresenhamLine(x0, y0, x1, y1, ColorFromCurrentPalette(colorIndex)); + } + + void BresenhamLine(int x0, int y0, int x1, int y1, CRGB color) + { + int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; + for (;;) { + leds[XY16(x0, y0)] += color; + if (x0 == x1 && y0 == y1) break; + e2 = 2 * err; + if (e2 > dy) { + err += dy; + x0 += sx; + } + if (e2 < dx) { + err += dx; + y0 += sy; + } + } + } + + + CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) { + return ColorFromPalette(currentPalette, index, brightness, currentBlendType); + } + + CRGB HsvToRgb(uint8_t h, uint8_t s, uint8_t v) { + CHSV hsv = CHSV(h, s, v); + CRGB rgb; + hsv2rgb_spectrum(hsv, rgb); + return rgb; + } + + void NoiseVariablesSetup() { + noisesmoothing = 200; + + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + noise_scale_x = 6000; + noise_scale_y = 6000; + } + + void FillNoise() { + for (uint16_t i = 0; i < MATRIX_WIDTH; i++) { + uint32_t ioffset = noise_scale_x * (i - MATRIX_CENTRE_Y); + + for (uint16_t j = 0; j < MATRIX_HEIGHT; j++) { + uint32_t joffset = noise_scale_y * (j - MATRIX_CENTRE_Y); + + byte data = inoise16(noise_x + ioffset, noise_y + joffset, noise_z) >> 8; + + uint8_t olddata = noise[i][j]; + uint8_t newdata = scale8(olddata, noisesmoothing) + scale8(data, 256 - noisesmoothing); + data = newdata; + + noise[i][j] = data; + } + } + } + + // non leds2 memory version. + void MoveX(byte delta) + { + + CRGB tmp = 0; + + for (int y = 0; y < MATRIX_HEIGHT; y++) + { + + // Shift Left: https://codedost.com/c/arraypointers-in-c/c-program-shift-elements-array-left-direction/ + // Computationally heavier but doesn't need an entire leds2 array + + tmp = leds[XY16(0, y)]; + for (int m = 0; m < delta; m++) + { + // Do this delta time for each row... computationally expensive potentially. + for(int x = 0; x < MATRIX_WIDTH; x++) + { + leds[XY16(x, y)] = leds [XY16(x+1, y)]; + } + + leds[XY16(MATRIX_WIDTH-1, y)] = tmp; + } + + + /* + // Shift + for (int x = 0; x < MATRIX_WIDTH - delta; x++) { + leds2[XY(x, y)] = leds[XY(x + delta, y)]; + } + + // Wrap around + for (int x = MATRIX_WIDTH - delta; x < MATRIX_WIDTH; x++) { + leds2[XY(x, y)] = leds[XY(x + delta - MATRIX_WIDTH, y)]; + } + */ + } // end row loop + + /* + // write back to leds + for (uint8_t y = 0; y < MATRIX_HEIGHT; y++) { + for (uint8_t x = 0; x < MATRIX_WIDTH; x++) { + leds[XY(x, y)] = leds2[XY(x, y)]; + } + } + */ + } + + void MoveY(byte delta) + { + + CRGB tmp = 0; + for (int x = 0; x < MATRIX_WIDTH; x++) + { + tmp = leds[XY16(x, 0)]; + for (int m = 0; m < delta; m++) // moves + { + // Do this delta time for each row... computationally expensive potentially. + for(int y = 0; y < MATRIX_HEIGHT; y++) + { + leds[XY16(x, y)] = leds [XY16(x, y+1)]; + } + + leds[XY16(x, MATRIX_HEIGHT-1)] = tmp; + } + } // end column loop + } /// MoveY + + +}; + +#endif \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Geometry.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Geometry.h new file mode 100644 index 0000000..4e47557 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Geometry.h @@ -0,0 +1,150 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from Noel Bundy's work: https://github.com/TwystNeko/Object3d + * Copyright (c) 2014 Noel Bundy + * + * Portions of this code are adapted from the Petty library: https://code.google.com/p/peggy/ + * Copyright (c) 2008 Windell H Oskay. All right reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Geometry_H +#define Geometry_H + +struct Vertex +{ + float x, y, z; + Vertex() + { + this->set(0, 0, 0); + } + + Vertex(float x, float y, float z) + { + this->set(x, y, z); + } + + void set(float x, float y, float z) + { + this->x = x; + this->y = y; + this->z = z; + } +}; + +struct EdgePoint +{ + int x, y; + boolean visible; + + EdgePoint() + { + this->set(0, 0); + this->visible = false; + } + + void set(int a, int b) + { + this->x = a; + this->y = b; + } +}; + +struct Point +{ + float x, y; + + Point() + { + set(0, 0); + } + + Point(float x, float y) + { + set(x, y); + } + + void set(float x, float y) + { + this->x = x; + this->y = y; + } + +}; + +struct squareFace +{ + int length; + int sommets[4]; + int ed[4]; + + squareFace() + { + set(-1, -1, -1, -1); + } + + squareFace(int a, int b, int c, int d) + { + this->length = 4; + this->sommets[0] = a; + this->sommets[1] = b; + this->sommets[2] = c; + this->sommets[3] = d; + } + + void set(int a, int b, int c, int d) + { + this->length = 4; + this->sommets[0] = a; + this->sommets[1] = b; + this->sommets[2] = c; + this->sommets[3] = d; + } + +}; + +struct triFace +{ + int length; + int sommets[3]; + int ed[3]; + + triFace() + { + set(-1,-1,-1); + } + triFace(int a, int b, int c) + { + this->length =3; + this->sommets[0]=a; + this->sommets[1]=b; + this->sommets[2]=c; + } + void set(int a, int b, int c) + { + this->length =3; + this->sommets[0]=a; + this->sommets[1]=b; + this->sommets[2]=c; + } +}; + +#endif \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternAttract.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternAttract.h new file mode 100644 index 0000000..dcb6491 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternAttract.h @@ -0,0 +1,74 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternAttract_H + +class PatternAttract : public Drawable { +private: + const int count = 8; + Attractor attractor; + +public: + PatternAttract() { + name = (char *)"Attract"; + } + + void start() { + int direction = random(0, 2); + if (direction == 0) + direction = -1; + + for (int i = 0; i < count; i++) { + Boid boid = Boid(15, 31 - i); + boid.mass = 1; // random(0.1, 2); + boid.velocity.x = ((float) random(40, 50)) / 100.0; + boid.velocity.x *= direction; + boid.velocity.y = 0; + boid.colorIndex = i * 32; + boids[i] = boid; + //dim = random(170, 250); + } + } + + unsigned int drawFrame() { + // dim all pixels on the display + uint8_t dim = beatsin8(2, 170, 250); + effects.DimAll(dim); + + for (int i = 0; i < count; i++) { + Boid boid = boids[i]; + + PVector force = attractor.attract(boid); + boid.applyForce(force); + + boid.update(); + effects.drawBackgroundFastLEDPixelCRGB(boid.location.x, boid.location.y, effects.ColorFromCurrentPalette(boid.colorIndex)); + + boids[i] = boid; + } + + effects.ShowFrame(); + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternBounce.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternBounce.h new file mode 100644 index 0000000..7340514 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternBounce.h @@ -0,0 +1,73 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternBounce_H + +class PatternBounce : public Drawable { +private: + static const int count = 32; + PVector gravity = PVector(0, 0.0125); + +public: + PatternBounce() { + name = (char *)"Bounce"; + } + + void start() { + unsigned int colorWidth = 256 / count; + for (int i = 0; i < count; i++) { + Boid boid = Boid(i, 0); + boid.velocity.x = 0; + boid.velocity.y = i * -0.01; + boid.colorIndex = colorWidth * i; + boid.maxforce = 10; + boid.maxspeed = 10; + boids[i] = boid; + } + } + + unsigned int drawFrame() { + // dim all pixels on the display + effects.DimAll(170); effects.ShowFrame(); + + for (int i = 0; i < count; i++) { + Boid boid = boids[i]; + + boid.applyForce(gravity); + + boid.update(); + + effects.drawBackgroundFastLEDPixelCRGB(boid.location.x, boid.location.y, effects.ColorFromCurrentPalette(boid.colorIndex)); + + if (boid.location.y >= MATRIX_HEIGHT - 1) { + boid.location.y = MATRIX_HEIGHT - 1; + boid.velocity.y *= -1.0; + } + + boids[i] = boid; + } + + return 15; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternCube.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternCube.h new file mode 100644 index 0000000..f2c60bc --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternCube.h @@ -0,0 +1,219 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from Noel Bundy's work: https://github.com/TwystNeko/Object3d + * Copyright (c) 2014 Noel Bundy + * + * Portions of this code are adapted from the Petty library: https://code.google.com/p/peggy/ + * Copyright (c) 2008 Windell H Oskay. All right reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternCube_H +#define PatternCube_H + +class PatternCube : public Drawable { + private: + float focal = 30; // Focal of the camera + int cubeWidth = 28; // Cube size + float Angx = 20.0, AngxSpeed = 0.05; // rotation (angle+speed) around X-axis + float Angy = 10.0, AngySpeed = 0.05; // rotation (angle+speed) around Y-axis + float Ox = 15.5, Oy = 15.5; // position (x,y) of the frame center + int zCamera = 110; // distance from cube to the eye of the camera + + // Local vertices + Vertex local[8]; + // Camera aligned vertices + Vertex aligned[8]; + // On-screen projected vertices + Point screen[8]; + // Faces + squareFace face[6]; + // Edges + EdgePoint edge[12]; + int nbEdges; + // ModelView matrix + float m00, m01, m02, m10, m11, m12, m20, m21, m22; + + // constructs the cube + void make(int w) + { + nbEdges = 0; + + local[0].set(-w, w, w); + local[1].set(w, w, w); + local[2].set(w, -w, w); + local[3].set(-w, -w, w); + local[4].set(-w, w, -w); + local[5].set(w, w, -w); + local[6].set(w, -w, -w); + local[7].set(-w, -w, -w); + + face[0].set(1, 0, 3, 2); + face[1].set(0, 4, 7, 3); + face[2].set(4, 0, 1, 5); + face[3].set(4, 5, 6, 7); + face[4].set(1, 2, 6, 5); + face[5].set(2, 3, 7, 6); + + int f, i; + for (f = 0; f < 6; f++) + { + for (i = 0; i < face[f].length; i++) + { + face[f].ed[i] = this->findEdge(face[f].sommets[i], face[f].sommets[i ? i - 1 : face[f].length - 1]); + } + } + } + + // finds edges from faces + int findEdge(int a, int b) + { + int i; + for (i = 0; i < nbEdges; i++) + if ((edge[i].x == a && edge[i].y == b) || (edge[i].x == b && edge[i].y == a)) + return i; + edge[nbEdges++].set(a, b); + return i; + } + + // rotates according to angle x&y + void rotate(float angx, float angy) + { + int i; + float cx = cos(angx); + float sx = sin(angx); + float cy = cos(angy); + float sy = sin(angy); + + m00 = cy; + m01 = 0; + m02 = -sy; + m10 = sx * sy; + m11 = cx; + m12 = sx * cy; + m20 = cx * sy; + m21 = -sx; + m22 = cx * cy; + + for (i = 0; i < 8; i++) + { + aligned[i].x = m00 * local[i].x + m01 * local[i].y + m02 * local[i].z; + aligned[i].y = m10 * local[i].x + m11 * local[i].y + m12 * local[i].z; + aligned[i].z = m20 * local[i].x + m21 * local[i].y + m22 * local[i].z + zCamera; + + screen[i].x = floor((Ox + focal * aligned[i].x / aligned[i].z)); + screen[i].y = floor((Oy - focal * aligned[i].y / aligned[i].z)); + } + + for (i = 0; i < 12; i++) + edge[i].visible = false; + + Point *pa, *pb, *pc; + for (i = 0; i < 6; i++) + { + pa = screen + face[i].sommets[0]; + pb = screen + face[i].sommets[1]; + pc = screen + face[i].sommets[2]; + + boolean back = ((pb->x - pa->x) * (pc->y - pa->y) - (pb->y - pa->y) * (pc->x - pa->x)) < 0; + if (!back) + { + int j; + for (j = 0; j < 4; j++) + { + edge[face[i].ed[j]].visible = true; + } + } + } + } + + byte hue = 0; + int step = 0; + + public: + PatternCube() { + name = (char *)"Cube"; + make(cubeWidth); + } + + unsigned int drawFrame() { + uint8_t blurAmount = beatsin8(2, 10, 255); + +#if FASTLED_VERSION >= 3001000 + blur2d(effects.leds, MATRIX_WIDTH, MATRIX_HEIGHT, blurAmount); +#else + effects.DimAll(blurAmount); effects.ShowFrame(); +#endif + + zCamera = beatsin8(2, 100, 140); + AngxSpeed = beatsin8(3, 1, 10) / 100.0f; + AngySpeed = beatcos8(5, 1, 10) / 100.0f; + + // Update values + Angx += AngxSpeed; + Angy += AngySpeed; + if (Angx >= TWO_PI) + Angx -= TWO_PI; + if (Angy >= TWO_PI) + Angy -= TWO_PI; + + rotate(Angx, Angy); + + // Draw cube + int i; + + CRGB color = effects.ColorFromCurrentPalette(hue, 128); + + // Backface + EdgePoint *e; + for (i = 0; i < 12; i++) + { + e = edge + i; + if (!e->visible) { + dma_display->drawLine(screen[e->x].x, screen[e->x].y, screen[e->y].x, screen[e->y].y, color); + } + } + + color = effects.ColorFromCurrentPalette(hue, 255); + + // Frontface + for (i = 0; i < 12; i++) + { + e = edge + i; + if (e->visible) + { + dma_display->drawLine(screen[e->x].x, screen[e->x].y, screen[e->y].x, screen[e->y].y, color); + } + } + + step++; + if (step == 8) { + step = 0; + hue++; + } + + effects.ShowFrame(); + + return 20; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternElectricMandala.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternElectricMandala.h new file mode 100644 index 0000000..880de25 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternElectricMandala.h @@ -0,0 +1,116 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Funky Noise" by Stefan Petrick: https://github.com/StefanPetrick/FunkyNoise + * Copyright (c) 2014 Stefan Petrick + * http://www.stefan-petrick.de/wordpress_beta + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternElectricMandala_H + +class PatternElectricMandala : public Drawable { + private: + + // The coordinates for 16-bit noise spaces. +#define NUM_LAYERS 1 + + // used for the random based animations + int16_t dx; + int16_t dy; + int16_t dz; + int16_t dsx; + int16_t dsy; + + public: + PatternElectricMandala() { + name = (char *)"ElectricMandala"; + } + + void start() { + // set to reasonable values to avoid a black out + noisesmoothing = 200; + + // just any free input pin + //random16_add_entropy(analogRead(18)); + + // fill coordinates with random values + // set zoom levels + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + noise_scale_x = 6000; + noise_scale_y = 6000; + + // for the random movement + dx = random8(); + dy = random8(); + dz = random8(); + dsx = random8(); + dsy = random8(); + } + + unsigned int drawFrame() { +#if FASTLED_VERSION >= 3001000 + // a new parameter set every 15 seconds + EVERY_N_SECONDS(15) { + //SetupRandomPalette3(); + dy = random16(500) - 250; // random16(2000) - 1000 is pretty fast but works fine, too + dx = random16(500) - 250; + dz = random16(500) - 250; + noise_scale_x = random16(10000) + 2000; + noise_scale_y = random16(10000) + 2000; + } +#endif + + noise_y += dy; + noise_x += dx; + noise_z += dz; + + effects.FillNoise(); + ShowNoiseLayer(0, 1, 0); + + effects.Caleidoscope3(); + effects.Caleidoscope1(); + + effects.ShowFrame(); + + return 30; + } + + // show just one layer + void ShowNoiseLayer(byte layer, byte colorrepeat, byte colorshift) { + for (uint16_t i = 0; i < MATRIX_WIDTH; i++) { + for (uint16_t j = 0; j < MATRIX_HEIGHT; j++) { + + uint8_t color = noise[i][j]; + + uint8_t bri = color; + + // assign a color depending on the actual palette + CRGB pixel = ColorFromPalette(effects.currentPalette, colorrepeat * (color + colorshift), bri); + + effects.leds[XY16(i, j)] = pixel; + } + } + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFire.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFire.h new file mode 100644 index 0000000..83aa67c --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFire.h @@ -0,0 +1,118 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/tree/master/examples/Fire2012WithPalette + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternFire_H +#define PatternFire_H + +#ifndef Effects_H +#include "Effects.h" +#endif + +class PatternFire : public Drawable { + private: + + public: + PatternFire() { + name = (char *)"Fire"; + } + + // There are two main parameters you can play with to control the look and + // feel of your fire: COOLING (used in step 1 above), and SPARKING (used + // in step 3 above). + // + // cooling: How much does the air cool as it rises? + // Less cooling = taller flames. More cooling = shorter flames. + // Default 55, suggested range 20-100 + int cooling = 100; + + // sparking: What chance (out of 255) is there that a new spark will be lit? + // Higher chance = more roaring fire. Lower chance = more flickery fire. + // Default 120, suggested range 50-200. + unsigned int sparking = 100; + + unsigned int drawFrame() { + // Add entropy to random number generator; we use a lot of it. + random16_add_entropy( random16()); + + effects.DimAll(235); + + for (int x = 0; x < MATRIX_WIDTH; x++) { + // Step 1. Cool down every cell a little + for (int y = 0; y < MATRIX_HEIGHT; y++) { + int xy = XY(x, y); + heat[xy] = qsub8(heat[xy], random8(0, ((cooling * 10) / MATRIX_HEIGHT) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int y = 0; y < MATRIX_HEIGHT; y++) { + heat[XY(x, y)] = (heat[XY(x, y + 1)] + heat[XY(x, y + 2)] + heat[XY(x, y + 2)]) / 3; + } + + // Step 2. Randomly ignite new 'sparks' of heat + if (random8() < sparking) { + // int x = (p[0] + p[1] + p[2]) / 3; + + int xy = XY(x, MATRIX_HEIGHT - 1); + heat[xy] = qadd8(heat[xy], random8(160, 255)); + } + + // Step 4. Map from heat cells to LED colors + for (int y = 0; y < MATRIX_HEIGHT; y++) { + int xy = XY(x, y); + byte colorIndex = heat[xy]; + + // Recommend that you use values 0-240 rather than + // the usual 0-255, as the last 15 colors will be + // 'wrapping around' from the hot end to the cold end, + // which looks wrong. + colorIndex = scale8(colorIndex, 200); + + // override color 0 to ensure a black background? + if (colorIndex != 0) + // effects.leds[xy] = CRGB::Black; + // else + effects.leds[xy] = effects.ColorFromCurrentPalette(colorIndex); + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(2); + effects.MoveFractionalNoiseX(2); + + + effects.ShowFrame(); + + return 15; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFlock.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFlock.h new file mode 100644 index 0000000..3ae31b1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFlock.h @@ -0,0 +1,125 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Flocking" in "The Nature of Code" by Daniel Shiffman: http://natureofcode.com/ + * Copyright (c) 2014 Daniel Shiffman + * http://www.shiffman.net + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Flocking +// Daniel Shiffman +// The Nature of Code, Spring 2009 + +// Demonstration of Craig Reynolds' "Flocking" behavior +// See: http://www.red3d.com/cwr/ +// Rules: Cohesion, Separation, Alignment + +#ifndef PatternFlock_H +#define PatternFlock_H + +class PatternFlock : public Drawable { + public: + PatternFlock() { + name = (char *)"Flock"; + } + + static const int boidCount = 10; + Boid predator; + + PVector wind; + byte hue = 0; + bool predatorPresent = true; + + void start() { + for (int i = 0; i < boidCount; i++) { + boids[i] = Boid(15, 15); + boids[i].maxspeed = 0.380; + boids[i].maxforce = 0.015; + } + + predatorPresent = random(0, 2) >= 1; + if (predatorPresent) { + predator = Boid(31, 31); + predatorPresent = true; + predator.maxspeed = 0.385; + predator.maxforce = 0.020; + predator.neighbordist = 16.0; + predator.desiredseparation = 0.0; + } + } + + unsigned int drawFrame() { + effects.DimAll(230); effects.ShowFrame(); + + bool applyWind = random(0, 255) > 250; + if (applyWind) { + wind.x = Boid::randomf() * .015; + wind.y = Boid::randomf() * .015; + } + + CRGB color = effects.ColorFromCurrentPalette(hue); + + for (int i = 0; i < boidCount; i++) { + Boid * boid = &boids[i]; + + if (predatorPresent) { + // flee from predator + boid->repelForce(predator.location, 10); + } + + boid->run(boids, boidCount); + boid->wrapAroundBorders(); + PVector location = boid->location; + // PVector velocity = boid->velocity; + // backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color); + // effects.leds[XY(location.x, location.y)] += color; + effects.drawBackgroundFastLEDPixelCRGB(location.x, location.y, color); + + if (applyWind) { + boid->applyForce(wind); + applyWind = false; + } + } + + if (predatorPresent) { + predator.run(boids, boidCount); + predator.wrapAroundBorders(); + color = effects.ColorFromCurrentPalette(hue + 128); + PVector location = predator.location; + // PVector velocity = predator.velocity; + // backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color); + // effects.leds[XY(location.x, location.y)] += color; + effects.drawBackgroundFastLEDPixelCRGB(location.x, location.y, color); + } + + EVERY_N_MILLIS(200) { + hue++; + } + + EVERY_N_SECONDS(30) { + predatorPresent = !predatorPresent; + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFlowField.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFlowField.h new file mode 100644 index 0000000..38f1083 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternFlowField.h @@ -0,0 +1,92 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternFlowField_H + +class PatternFlowField : public Drawable { + public: + PatternFlowField() { + name = (char *)"FlowField"; + } + + uint16_t x; + uint16_t y; + uint16_t z; + + uint16_t speed = 1; + uint16_t scale = 26; + + static const int count = 40; + + byte hue = 0; + + void start() { + x = random16(); + y = random16(); + z = random16(); + + for (int i = 0; i < count; i++) { + boids[i] = Boid(random(MATRIX_WIDTH), 0); + } + } + + unsigned int drawFrame() { + effects.DimAll(240); + + // CRGB color = effects.ColorFromCurrentPalette(hue); + + for (int i = 0; i < count; i++) { + Boid * boid = &boids[i]; + + int ioffset = scale * boid->location.x; + int joffset = scale * boid->location.y; + + byte angle = inoise8(x + ioffset, y + joffset, z); + + boid->velocity.x = (float) sin8(angle) * 0.0078125 - 1.0; + boid->velocity.y = -((float)cos8(angle) * 0.0078125 - 1.0); + boid->update(); + + effects.drawBackgroundFastLEDPixelCRGB(boid->location.x, boid->location.y, effects.ColorFromCurrentPalette(angle + hue)); // color + + if (boid->location.x < 0 || boid->location.x >= MATRIX_WIDTH || + boid->location.y < 0 || boid->location.y >= MATRIX_HEIGHT) { + boid->location.x = random(MATRIX_WIDTH); + boid->location.y = 0; + } + } + + EVERY_N_MILLIS(200) { + hue++; + } + + x += speed; + y += speed; + z += speed; + + effects.ShowFrame(); + + return 50; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternIncrementalDrift.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternIncrementalDrift.h new file mode 100644 index 0000000..f68c04f --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternIncrementalDrift.h @@ -0,0 +1,51 @@ +/* +* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternIncrementalDrift_H +#define PatternIncrementalDrift_H + +class PatternIncrementalDrift : public Drawable { + public: + PatternIncrementalDrift() { + name = (char *)"Incremental Drift"; + } + + unsigned int drawFrame() { + uint8_t dim = beatsin8(2, 230, 250); + effects.DimAll(dim); effects.ShowFrame(); + + for (int i = 2; i <= MATRIX_WIDTH / 2; i++) + { + CRGB color = effects.ColorFromCurrentPalette((i - 2) * (240 / (MATRIX_WIDTH / 2))); + + uint8_t x = beatcos8((17 - i) * 2, MATRIX_CENTER_X - i, MATRIX_CENTER_X + i); + uint8_t y = beatsin8((17 - i) * 2, MATRIX_CENTER_Y - i, MATRIX_CENTER_Y + i); + + effects.drawBackgroundFastLEDPixelCRGB(x, y, color); + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternIncrementalDrift2.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternIncrementalDrift2.h new file mode 100644 index 0000000..0ce06c2 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternIncrementalDrift2.h @@ -0,0 +1,63 @@ +/* +* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternIncrementalDrift2_H +#define PatternIncrementalDrift2_H + +class PatternIncrementalDrift2 : public Drawable { + public: + PatternIncrementalDrift2() { + name = (char *)"Incremental Drift Rose"; + } + + unsigned int drawFrame() { + uint8_t dim = beatsin8(2, 170, 250); + effects.DimAll(dim); effects.ShowFrame(); + + for (uint8_t i = 0; i < 32; i++) + { + CRGB color; + + uint8_t x = 0; + uint8_t y = 0; + + if (i < 16) { + x = beatcos8((i + 1) * 2, i, MATRIX_WIDTH - i); + y = beatsin8((i + 1) * 2, i, MATRIX_HEIGHT - i); + color = effects.ColorFromCurrentPalette(i * 14); + } + else + { + x = beatsin8((32 - i) * 2, MATRIX_WIDTH - i, i + 1); + y = beatcos8((32 - i) * 2, MATRIX_HEIGHT - i, i + 1); + color = effects.ColorFromCurrentPalette((31 - i) * 14); + } + + effects.drawBackgroundFastLEDPixelCRGB(x, y, color); + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternInfinity.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternInfinity.h new file mode 100644 index 0000000..0c068ad --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternInfinity.h @@ -0,0 +1,61 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternInfinity_H + +class PatternInfinity : public Drawable { +public: + PatternInfinity() { + name = (char *)"Infinity"; + } + + unsigned int drawFrame() { + // dim all pixels on the display slightly + // to 250/255 (98%) of their current brightness + blur2d(effects.leds, MATRIX_WIDTH > 255 ? 255 : MATRIX_WIDTH, MATRIX_HEIGHT > 255 ? 255 : MATRIX_HEIGHT, 250); + // effects.DimAll(250); effects.ShowFrame(); + + + // the Effects class has some sample oscillators + // that move from 0 to 255 at different speeds + effects.MoveOscillators(); + + // the horizontal position of the head of the infinity sign + // oscillates from 0 to the maximum horizontal and back + int x = (MATRIX_WIDTH - 1) - effects.p[1]; + + // the vertical position of the head oscillates + // from 8 to 23 and back (hard-coded for a 32x32 matrix) + int y = map8(sin8(effects.osci[3]), 8, 23); + + // the hue oscillates from 0 to 255, overflowing back to 0 + byte hue = sin8(effects.osci[5]); + + // draw a pixel at x,y using a color from the current palette + effects.Pixel(x, y, hue); + + effects.ShowFrame(); + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternInvaders.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternInvaders.h new file mode 100644 index 0000000..e2df82e --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternInvaders.h @@ -0,0 +1,154 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Inspired by 'Space Invader Generator': https://the8bitpimp.wordpress.com/2013/05/07/space-invader-generator + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternInvaders_H +#define PatternInvaders_H + +class PatternInvadersSmall : public Drawable { + private: + uint8_t x = 1; + uint8_t y = 1; + + public: + PatternInvadersSmall() { + name = (char *)"Invaders Small"; + } + + void start() { + dma_display->fillScreen(0); + } + + unsigned int drawFrame() { + CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255)); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 5; j++) { + CRGB color = CRGB::Black; + + if (random(0, 2) == 1) color = color1; + + effects.drawBackgroundFastLEDPixelCRGB(x + i, y + j, color); + + if (i < 2) + effects.drawBackgroundFastLEDPixelCRGB(x + (4 - i), y + j, color); + } + } + + x += 6; + if (x > 25) { + x = 1; + y += 6; + } + + if (y > 25) y = x = 1; + + effects.ShowFrame(); + + return 125; + } +}; + +class PatternInvadersMedium : public Drawable { + private: + uint8_t x = 0; + uint8_t y = 0; + + public: + PatternInvadersMedium() { + name = (char *)"Invaders Medium"; + } + + void start() { + dma_display->fillScreen(0); + } + + unsigned int drawFrame() { + CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255)); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 5; j++) { + CRGB color = CRGB::Black; + + if (random(0, 2) == 1) color = color1; + + dma_display->fillRect(x + (i * 2), y + (j * 2), x + (i * 2 + 1), y + (j * 2 + 1), color); + + if (i < 2) + dma_display->fillRect(x + (8 - i * 2), y + (j * 2), x + (9 - i * 2), y + (j * 2 + 1), color); + } + } + + x += 11; + if (x > 22) { + x = 0; + y += 11; + } + + if (y > 22) y = x = 0; + + effects.ShowFrame(); + + return 500; + } +}; + +class PatternInvadersLarge : public Drawable { + private: + + public: + PatternInvadersLarge() { + name = (char *)"Invaders Large"; + } + + void start() { + dma_display->fillScreen(0); + } + + unsigned int drawFrame() { + dma_display->fillScreen(0); + + CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255)); + + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 5; y++) { + CRGB color = CRGB::Black; + + if (random(0, 2) == 1) { + color = color1; + } + + dma_display->fillRect(1 + x * 6, 1 + y * 6, 5 + x * 6, 5 + y * 6, color); + + if (x < 2) + dma_display->fillRect(1 + (4 - x) * 6, 1 + y * 6, 5 + (4 - x) * 6, 5 + y * 6, color); + } + } + + effects.ShowFrame(); + + return 2000; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternLife.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternLife.h new file mode 100644 index 0000000..7c9ef30 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternLife.h @@ -0,0 +1,129 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from Andrew: http://pastebin.com/f22bfe94d + * which, in turn, was "Adapted from the Life example on the Processing.org site" + * + * Made much more colorful by J.B. Langston: https://github.com/jblang/aurora/commit/6db5a884e3df5d686445c4f6b669f1668841929b + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternLife_H +#define PatternLife_H + +class Cell { +public: + byte alive : 1; + byte prev : 1; + byte hue: 6; + byte brightness; +}; + +class PatternLife : public Drawable { +private: + Cell world[MATRIX_WIDTH][MATRIX_HEIGHT]; + unsigned int density = 50; + int generation = 0; + + void randomFillWorld() { + for (int i = 0; i < MATRIX_WIDTH; i++) { + for (int j = 0; j < MATRIX_HEIGHT; j++) { + if (random(100) < density) { + world[i][j].alive = 1; + world[i][j].brightness = 255; + } + else { + world[i][j].alive = 0; + world[i][j].brightness = 0; + } + world[i][j].prev = world[i][j].alive; + world[i][j].hue = 0; + } + } + } + + int neighbours(int x, int y) { + return (world[(x + 1) % MATRIX_WIDTH][y].prev) + + (world[x][(y + 1) % MATRIX_HEIGHT].prev) + + (world[(x + MATRIX_WIDTH - 1) % MATRIX_WIDTH][y].prev) + + (world[x][(y + MATRIX_HEIGHT - 1) % MATRIX_HEIGHT].prev) + + (world[(x + 1) % MATRIX_WIDTH][(y + 1) % MATRIX_HEIGHT].prev) + + (world[(x + MATRIX_WIDTH - 1) % MATRIX_WIDTH][(y + 1) % MATRIX_HEIGHT].prev) + + (world[(x + MATRIX_WIDTH - 1) % MATRIX_WIDTH][(y + MATRIX_HEIGHT - 1) % MATRIX_HEIGHT].prev) + + (world[(x + 1) % MATRIX_WIDTH][(y + MATRIX_HEIGHT - 1) % MATRIX_HEIGHT].prev); + } + +public: + PatternLife() { + name = (char *)"Life"; + } + + unsigned int drawFrame() { + if (generation == 0) { + effects.ClearFrame(); + + randomFillWorld(); + } + + // Display current generation + for (int i = 0; i < MATRIX_WIDTH; i++) { + for (int j = 0; j < MATRIX_HEIGHT; j++) { + effects.leds[XY(i, j)] = effects.ColorFromCurrentPalette(world[i][j].hue * 4, world[i][j].brightness); + } + } + + // Birth and death cycle + for (int x = 0; x < MATRIX_WIDTH; x++) { + for (int y = 0; y < MATRIX_HEIGHT; y++) { + // Default is for cell to stay the same + if (world[x][y].brightness > 0 && world[x][y].prev == 0) + world[x][y].brightness *= 0.9; + int count = neighbours(x, y); + if (count == 3 && world[x][y].prev == 0) { + // A new cell is born + world[x][y].alive = 1; + world[x][y].hue += 2; + world[x][y].brightness = 255; + } else if ((count < 2 || count > 3) && world[x][y].prev == 1) { + // Cell dies + world[x][y].alive = 0; + } + } + } + + // Copy next generation into place + for (int x = 0; x < MATRIX_WIDTH; x++) { + for (int y = 0; y < MATRIX_HEIGHT; y++) { + world[x][y].prev = world[x][y].alive; + } + } + + + generation++; + if (generation >= 256) + generation = 0; + + effects.ShowFrame(); + + return 60; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternMaze.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternMaze.h new file mode 100644 index 0000000..c469922 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternMaze.h @@ -0,0 +1,264 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Many thanks to Jamis Buck for the documentation of the Growing Tree maze generation algorithm: http://weblog.jamisbuck.org/2011/1/27/maze-generation-growing-tree-algorithm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternMaze_H +#define PatternMaze_H + +class PatternMaze : public Drawable { +private: + enum Directions { + None = 0, + Up = 1, + Down = 2, + Left = 4, + Right = 8, + }; + + struct Point{ + int x; + int y; + + static Point New(int x, int y) { + Point point; + point.x = x; + point.y = y; + return point; + } + + Point Move(Directions direction) { + switch (direction) + { + case Up: + return New(x, y - 1); + + case Down: + return New(x, y + 1); + + case Left: + return New(x - 1, y); + + case Right: + default: + return New(x + 1, y); + } + } + + static Directions Opposite(Directions direction) { + switch (direction) { + case Up: + return Down; + + case Down: + return Up; + + case Left: + return Right; + + case Right: + default: + return Left; + } + } + }; + +// int width = 16; +// int height = 16; + + static const int width = MATRIX_WIDTH / 2; + static const int height = MATRIX_HEIGHT / 2; + + + Directions grid[width][height]; + + Point point; + + Point cells[256]; + int cellCount = 0; + + int algorithm = 0; + int algorithmCount = 1; + + byte hue = 0; + byte hueOffset = 0; + + Directions directions[4] = { Up, Down, Left, Right }; + + void removeCell(int index) {// shift cells after index down one + for (int i = index; i < cellCount - 1; i++) { + cells[i] = cells[i + 1]; + } + + cellCount--; + } + + void shuffleDirections() { + for (int a = 0; a < 4; a++) + { + int r = random(a, 4); + Directions temp = directions[a]; + directions[a] = directions[r]; + directions[r] = temp; + } + } + + Point createPoint(int x, int y) { + Point point; + point.x = x; + point.y = y; + return point; + } + + CRGB chooseColor(int index) { + byte h = index + hueOffset; + + switch (algorithm) { + case 0: + default: + return effects.ColorFromCurrentPalette(h); + + case 1: + return effects.ColorFromCurrentPalette(hue++); + } + } + + int chooseIndex(int max) { + switch (algorithm) { + case 0: + default: + // choose newest (recursive backtracker) + return max - 1; + + case 1: + // choose random(Prim's) + return random(max); + + // case 2: + // // choose oldest (not good, so disabling) + // return 0; + } + } + + void generateMaze() { + while (cellCount > 1) { + drawNextCell(); + } + } + + void drawNextCell() { + int index = chooseIndex(cellCount); + + if (index < 0) + return; + + point = cells[index]; + + Point imagePoint = createPoint(point.x * 2, point.y * 2); + + //effects.drawBackgroundFastLEDPixelCRGB(imagePoint.x, imagePoint.y, CRGB(CRGB::Gray)); + + shuffleDirections(); + + CRGB color = chooseColor(index); + + for (int i = 0; i < 4; i++) { + Directions direction = directions[i]; + + Point newPoint = point.Move(direction); + if (newPoint.x >= 0 && newPoint.y >= 0 && newPoint.x < width && newPoint.y < height && grid[newPoint.y][newPoint.x] == None) { + grid[point.y][point.x] = (Directions) ((int) grid[point.y][point.x] | (int) direction); + grid[newPoint.y][newPoint.x] = (Directions) ((int) grid[newPoint.y][newPoint.x] | (int) point.Opposite(direction)); + + Point newImagePoint = imagePoint.Move(direction); + + effects.drawBackgroundFastLEDPixelCRGB(newImagePoint.x, newImagePoint.y, color); + + cellCount++; + cells[cellCount - 1] = newPoint; + + index = -1; + break; + } + } + + if (index > -1) { + Point finishedPoint = cells[index]; + imagePoint = createPoint(finishedPoint.x * 2, finishedPoint.y * 2); + effects.drawBackgroundFastLEDPixelCRGB(imagePoint.x, imagePoint.y, color); + + removeCell(index); + } + } + +public: + PatternMaze() { + name = (char *)"Maze"; + } + + unsigned int drawFrame() { + if (cellCount < 1) { + + effects.ClearFrame(); + + // reset the maze grid + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + grid[y][x] = None; + } + } + + int x = random(width); + int y = random(height); + + cells[0] = createPoint(x, y); + + cellCount = 1; + + hue = 0; + hueOffset = random(0, 256); + + } + + drawNextCell(); + + if (cellCount < 1) { + algorithm++; + if (algorithm >= algorithmCount) + algorithm = 0; + + return 0; + } + + effects.ShowFrame(); + + return 0; + } + + void start() { + effects.ClearFrame(); + cellCount = 0; + hue = 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternMunch.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternMunch.h new file mode 100644 index 0000000..f580828 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternMunch.h @@ -0,0 +1,73 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Munch pattern created by J.B. Langston: https://github.com/jblang/aurora/blob/master/PatternMunch.h + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternMunch_H +#define PatternMunch_H + + +class PatternMunch : public Drawable { +private: + byte count = 0; + byte dir = 1; + byte flip = 0; + byte generation = 0; + +public: + PatternMunch() { + name = (char *)"Munch"; + } + + unsigned int drawFrame() { + + for (uint16_t x = 0; x < MATRIX_WIDTH; x++) { + for (uint16_t y = 0; y < MATRIX_HEIGHT; y++) { + effects.leds[XY16(x, y)] = (x ^ y ^ flip) < count ? effects.ColorFromCurrentPalette(((x ^ y) << 2) + generation) : CRGB::Black; + + // The below is more pleasant + // effects.leds[XY(x, y)] = effects.ColorFromCurrentPalette(((x ^ y) << 2) + generation) ; + } + } + + count += dir; + + if (count <= 0 || count >= MATRIX_WIDTH) { + dir = -dir; + } + + if (count <= 0) { + if (flip == 0) + flip = MATRIX_WIDTH-1; + else + flip = 0; + } + + generation++; + + // show it ffs! + effects.ShowFrame(); + return 60; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternNoiseSmearing.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternNoiseSmearing.h new file mode 100644 index 0000000..ab7ed7b --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternNoiseSmearing.h @@ -0,0 +1,338 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Portions of this code are adapted from "Noise Smearing" by Stefan Petrick: https://gist.githubusercontent.com/embedded-creations/5cd47d83cb0e04f4574d/raw/ebf6a82b4755d55cfba3bf6598f7b19047f89daf/NoiseSmearing.ino +* Copyright (c) 2014 Stefan Petrick +* http://www.stefan-petrick.de/wordpress_beta +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternNoiseSmearing_H +#define PatternNoiseSmearing_H + +byte patternNoiseSmearingHue = 0; + +class PatternMultipleStream : public Drawable { +public: + PatternMultipleStream() { + name = (char *)"MultipleStream"; + } + + // this pattern draws two points to the screen based on sin/cos if a counter + // (comment out NoiseSmearWithRadius to see pattern of pixels) + // these pixels are smeared by a large radius, giving a lot of movement + // the image is dimmed before each drawing to not saturate the screen with color + // the smear has an offset so the pixels usually have a trail leading toward the upper left + unsigned int drawFrame() { + static unsigned long counter = 0; +#if 0 + // this counter lets put delays between each frame and still get the same animation + counter++; +#else + // this counter updates in real time and can't be slowed down for debugging + counter = millis() / 10; +#endif + + byte x1 = 4 + sin8(counter * 2) / 10; + byte x2 = 8 + sin8(counter * 2) / 16; + byte y2 = 8 + cos8((counter * 2) / 3) / 16; + + effects.leds[XY(x1, x2)] = effects.ColorFromCurrentPalette(patternNoiseSmearingHue); + effects.leds[XY(x2, y2)] = effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 128); + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(8); + effects.MoveFractionalNoiseX(); + + effects.MoveY(8); + effects.MoveFractionalNoiseY(); + + patternNoiseSmearingHue++; + + return 0; + } +}; + +class PatternMultipleStream2 : public Drawable { +public: + PatternMultipleStream2() { + name = (char *)"MultipleStream2"; + } + + unsigned int drawFrame() { + effects.DimAll(230); effects.ShowFrame(); + + byte xx = 4 + sin8(millis() / 9) / 10; + byte yy = 4 + cos8(millis() / 10) / 10; + effects.leds[XY(xx, yy)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue); + + xx = 8 + sin8(millis() / 10) / 16; + yy = 8 + cos8(millis() / 7) / 16; + effects.leds[XY(xx, yy)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 80); + + effects.leds[XY(15, 15)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 160); + + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + + patternNoiseSmearingHue++; + + return 0; + } +}; + +class PatternMultipleStream3 : public Drawable { +public: + PatternMultipleStream3() { + name = (char *)"MultipleStream3"; + } + + unsigned int drawFrame() { + //CLS(); + effects.DimAll(235); effects.ShowFrame(); + + for (uint8_t i = 3; i < 32; i = i + 4) { + effects.leds[XY(i, 15)] += effects.ColorFromCurrentPalette(i * 8); + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + + effects.ShowFrame(); + + return 1; + } +}; + +class PatternMultipleStream4 : public Drawable { +public: + PatternMultipleStream4() { + name = (char *)"MultipleStream4"; + } + + unsigned int drawFrame() { + + //CLS(); + effects.DimAll(235); effects.ShowFrame(); + + effects.leds[XY(15, 15)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue); + + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(8); + effects.MoveFractionalNoiseX(); + + effects.MoveY(8); + effects.MoveFractionalNoiseY(); + + patternNoiseSmearingHue++; + + return 0; + } +}; + +class PatternMultipleStream5 : public Drawable { +public: + PatternMultipleStream5() { + name = (char *)"MultipleStream5"; + } + + unsigned int drawFrame() { + + //CLS(); + effects.DimAll(235); effects.ShowFrame(); + + + for (uint8_t i = 3; i < 32; i = i + 4) { + effects.leds[XY(i, 31)] += effects.ColorFromCurrentPalette(i * 8); + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(4); + effects.MoveFractionalNoiseX(4); + + return 0; + } +}; + +class PatternMultipleStream8 : public Drawable { +public: + PatternMultipleStream8() { + name = (char *)"MultipleStream8"; + } + + unsigned int drawFrame() { + effects.DimAll(230); effects.ShowFrame(); + + // draw grid of rainbow dots on top of the dimmed image + for (uint8_t y = 1; y < 32; y = y + 6) { + for (uint8_t x = 1; x < 32; x = x + 6) { + + effects.leds[XY(x, y)] += effects.ColorFromCurrentPalette((x * y) / 4); + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseX(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseY(4); + + return 0; + } +}; + +class PatternPaletteSmear : public Drawable { +public: + PatternPaletteSmear() { + name = (char *)"PaletteSmear"; + } + + unsigned int drawFrame() { + + effects.DimAll(170); effects.ShowFrame(); + + // draw a rainbow color palette + for (uint8_t y = 0; y < MATRIX_HEIGHT; y++) { + for (uint8_t x = 0; x < MATRIX_WIDTH; x++) { + effects.leds[XY(x, y)] += effects.ColorFromCurrentPalette(x * 8, y * 8 + 7); + } + } + + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + + effects.FillNoise(); + + effects.MoveX(3); + //effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + effects.ShowFrame(); + + return 0; + } +}; + +class PatternRainbowFlag : public Drawable { +public: + PatternRainbowFlag() { + name = (char *)"RainbowFlag"; + } + + unsigned int drawFrame() { + effects.DimAll(10); effects.ShowFrame(); + + CRGB rainbow[7] = { + CRGB::Red, + CRGB::Orange, + CRGB::Yellow, + CRGB::Green, + CRGB::Blue, + CRGB::Violet + }; + + uint8_t y = 2; + + for (uint8_t c = 0; c < 6; c++) { + for (uint8_t j = 0; j < 5; j++) { + for (uint8_t x = 0; x < MATRIX_WIDTH; x++) { + effects.leds[XY(x, y)] += rainbow[c]; + } + + y++; + if (y >= MATRIX_HEIGHT) + break; + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + + return 0; + } +}; +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPendulumWave.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPendulumWave.h new file mode 100644 index 0000000..08fd5b7 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPendulumWave.h @@ -0,0 +1,66 @@ +/* +* +* Inspired by and based on a loading animation for Prismata by Lunarch Studios: +* http://www.reddit.com/r/gifs/comments/2on8si/connecting_to_server_so_mesmerizing/cmow0sz +* +* Lunarch Studios Inc. hereby publishes the Actionscript 3 source code pasted in this +* comment under the Creative Commons CC0 1.0 Universal Public Domain Dedication. +* Lunarch Studios Inc. waives all rights to the work worldwide under copyright law, +* including all related and neighboring rights, to the extent allowed by law. +* You can copy, modify, distribute and perform the work, even for commercial purposes, +* all without asking permission. +* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternPendulumWave_H +#define PatternPendulumWave_H + +#define WAVE_BPM 25 +#define AMP_BPM 2 +#define SKEW_BPM 4 +#define WAVE_TIMEMINSKEW MATRIX_WIDTH/8 +#define WAVE_TIMEMAXSKEW MATRIX_WIDTH/2 + +class PatternPendulumWave : public Drawable { + public: + PatternPendulumWave() { + name = (char *)"Pendulum Wave"; + } + + unsigned int drawFrame() { + effects.ClearFrame(); + + for (int x = 0; x < MATRIX_WIDTH; ++x) + { + uint16_t amp = beatsin16(AMP_BPM, MATRIX_HEIGHT/8, MATRIX_HEIGHT-1); + uint16_t offset = (MATRIX_HEIGHT - beatsin16(AMP_BPM, 0, MATRIX_HEIGHT))/2; + + uint8_t y = beatsin16(WAVE_BPM, 0, amp, x*beatsin16(SKEW_BPM, WAVE_TIMEMINSKEW, WAVE_TIMEMAXSKEW)) + offset; + + effects.drawBackgroundFastLEDPixelCRGB(x, y, effects.ColorFromCurrentPalette(x * 7)); + } + effects.ShowFrame(); + return 20; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPlasma.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPlasma.h new file mode 100644 index 0000000..99500b1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPlasma.h @@ -0,0 +1,66 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from LedEffects Plasma by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Plasma.cpp?at=default + * Copyright (c) 2013 Robert Atkins + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternPlasma_H +#define PatternPlasma_H + +class PatternPlasma : public Drawable { +private: + int time = 0; + int cycles = 0; + +public: + PatternPlasma() { + name = (char *)"Plasma"; + } + + unsigned int drawFrame() { + for (int x = 0; x < MATRIX_WIDTH; x++) { + for (int y = 0; y < MATRIX_HEIGHT; y++) { + int16_t v = 0; + uint8_t wibble = sin8(time); + v += sin16(x * wibble * 2 + time); + v += cos16(y * (128 - wibble) * 2 + time); + v += sin16(y * x * cos8(-time) / 2); + + effects.Pixel(x, y, (v >> 8) + 127); + } + } + + time += 1; + cycles++; + + if (cycles >= 2048) { + time = 0; + cycles = 0; + } + + effects.ShowFrame(); + + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPulse.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPulse.h new file mode 100644 index 0000000..4d4ba69 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternPulse.h @@ -0,0 +1,82 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Based at least in part on someone else's work that I can no longer find. + * Please let me know if you recognize any of this code! + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternPulse_H +#define PatternPulse_H + +class PatternPulse : public Drawable { + private: + int hue; + int centerX = 0; + int centerY = 0; + int step = -1; + int maxSteps = 16; + float fadeRate = 0.8; + int diff; + + public: + PatternPulse() { + name = (char *)"Pulse"; + } + + unsigned int drawFrame() { + effects.DimAll(235); + + if (step == -1) { + centerX = random(32); + centerY = random(32); + hue = random(256); // 170; + step = 0; + } + + if (step == 0) { + dma_display->drawCircle(centerX, centerY, step, effects.ColorFromCurrentPalette(hue)); + step++; + } + else { + if (step < maxSteps) { + // initial pulse + dma_display->drawCircle(centerX, centerY, step, effects.ColorFromCurrentPalette(hue, pow(fadeRate, step - 2) * 255)); + + // secondary pulse + if (step > 3) { + dma_display->drawCircle(centerX, centerY, step - 3, effects.ColorFromCurrentPalette(hue, pow(fadeRate, step - 2) * 255)); + } + step++; + } + else { + step = -1; + } + } + + effects.standardNoiseSmearing(); + + effects.ShowFrame(); + + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternRadar.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternRadar.h new file mode 100644 index 0000000..1dc4484 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternRadar.h @@ -0,0 +1,56 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternRadar_H + +class PatternRadar : public Drawable { + private: + byte theta = 0; + byte hueoffset = 0; + + public: + PatternRadar() { + name = (char *)"Radar"; + } + + unsigned int drawFrame() { + effects.DimAll(254); effects.ShowFrame(); + + for (int offset = 0; offset < MATRIX_CENTER_X; offset++) { + byte hue = 255 - (offset * 16 + hueoffset); + CRGB color = effects.ColorFromCurrentPalette(hue); + uint8_t x = mapcos8(theta, offset, (MATRIX_WIDTH - 1) - offset); + uint8_t y = mapsin8(theta, offset, (MATRIX_HEIGHT - 1) - offset); + uint16_t xy = XY(x, y); + effects.leds[xy] = color; + + EVERY_N_MILLIS(25) { + theta += 2; + hueoffset += 1; + } + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSimplexNoise.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSimplexNoise.h new file mode 100644 index 0000000..0c47bff --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSimplexNoise.h @@ -0,0 +1,79 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/blob/master/examples/Noise/Noise.ino + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSimplexNoise_H +#define PatternSimplexNoise_H + +class PatternSimplexNoise : public Drawable { + public: + PatternSimplexNoise() { + name = (char *)"Noise"; + } + + void start() { + // Initialize our coordinates to some random values + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + } + + unsigned int drawFrame() { +#if FASTLED_VERSION >= 3001000 + // a new parameter set every 15 seconds + EVERY_N_SECONDS(15) { + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + } +#endif + + uint32_t speed = 100; + + effects.FillNoise(); + ShowNoiseLayer(0, 1, 0); + + // noise_x += speed; + noise_y += speed; + noise_z += speed; + + effects.ShowFrame(); + + return 30; + } + + // show just one layer + void ShowNoiseLayer(byte layer, byte colorrepeat, byte colorshift) { + for (uint16_t i = 0; i < MATRIX_WIDTH; i++) { + for (uint16_t j = 0; j < MATRIX_HEIGHT; j++) { + uint8_t pixel = noise[i][j]; + + // assign a color depending on the actual palette + effects.leds[XY16(i, j)] = effects.ColorFromCurrentPalette(colorrepeat * (pixel + colorshift), pixel); + } + } + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSnake.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSnake.h new file mode 100644 index 0000000..e25756f --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSnake.h @@ -0,0 +1,145 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from LedEffects Snake by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Snake.cpp?at=default + * Copyright (c) 2013 Robert Atkins + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSnake_H +#define PatternSnake_H + +class PatternSnake : public Drawable { +private: + static const byte SNAKE_LENGTH = 16; + + CRGB colors[SNAKE_LENGTH]; + uint8_t initialHue; + + enum Direction { + UP, DOWN, LEFT, RIGHT + }; + + struct Pixel { + uint8_t x; + uint8_t y; + }; + + struct Snake { + Pixel pixels[SNAKE_LENGTH]; + + Direction direction; + + void newDirection() { + switch (direction) { + case UP: + case DOWN: + direction = random(0, 2) == 1 ? RIGHT : LEFT; + break; + + case LEFT: + case RIGHT: + direction = random(0, 2) == 1 ? DOWN : UP; + + default: + break; + } + } + + void shuffleDown() { + for (byte i = SNAKE_LENGTH - 1; i > 0; i--) { + pixels[i] = pixels[i - 1]; + } + } + + void reset() { + direction = UP; + for (int i = 0; i < SNAKE_LENGTH; i++) { + pixels[i].x = 0; + pixels[i].y = 0; + } + } + + void move() { + switch (direction) { + case UP: + pixels[0].y = (pixels[0].y + 1) % MATRIX_HEIGHT; + break; + case LEFT: + pixels[0].x = (pixels[0].x + 1) % MATRIX_WIDTH; + break; + case DOWN: + pixels[0].y = pixels[0].y == 0 ? MATRIX_HEIGHT - 1 : pixels[0].y - 1; + break; + case RIGHT: + pixels[0].x = pixels[0].x == 0 ? MATRIX_WIDTH - 1 : pixels[0].x - 1; + break; + } + } + + void draw(CRGB colors[SNAKE_LENGTH]) { + for (byte i = 0; i < SNAKE_LENGTH; i++) { + effects.leds[XY(pixels[i].x, pixels[i].y)] = colors[i] %= (255 - i * (255 / SNAKE_LENGTH)); + } + } + }; + + static const int snakeCount = 6; + Snake snakes[snakeCount]; + +public: + PatternSnake() { + name = (char *)"Snake"; + for (int i = 0; i < snakeCount; i++) { + Snake* snake = &snakes[i]; + snake->reset(); + } + } + + void start() + { + effects.ClearFrame(); + } + + unsigned int drawFrame() { + + + fill_palette(colors, SNAKE_LENGTH, initialHue++, 5, effects.currentPalette, 255, LINEARBLEND); + + for (int i = 0; i < snakeCount; i++) { + Snake* snake = &snakes[i]; + + snake->shuffleDown(); + + if (random(10) > 7) { + snake->newDirection(); + } + + snake->move(); + snake->draw(colors); + } + + effects.ShowFrame(); + + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpark.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpark.h new file mode 100644 index 0000000..74f3b16 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpark.h @@ -0,0 +1,113 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/tree/master/examples/Fire2012WithPalette + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSpark_H +#define PatternSpark_H + +class PatternSpark : public Drawable { + private: + + public: + PatternSpark() { + name = (char *)"Spark"; + } + + // There are two main parameters you can play with to control the look and + // feel of your fire: COOLING (used in step 1 above), and SPARKING (used + // in step 3 above). + // + // COOLING: How much does the air cool as it rises? + // Less cooling = taller flames. More cooling = shorter flames. + // Default 55, suggested range 20-100 + uint8_t cooling = 100; + + // SPARKING: What chance (out of 255) is there that a new spark will be lit? + // Higher chance = more roaring fire. Lower chance = more flickery fire. + // Default 120, suggested range 50-200. + uint8_t sparking = 50; + + unsigned int drawFrame() { + // Add entropy to random number generator; we use a lot of it. + random16_add_entropy( random16()); + + effects.DimAll(235); effects.ShowFrame(); + + for (uint8_t x = 0; x < MATRIX_WIDTH; x++) { + // Step 1. Cool down every cell a little + for (int y = 0; y < MATRIX_HEIGHT; y++) { + int xy = XY(x, y); + heat[xy] = qsub8(heat[xy], random8(0, ((cooling * 10) / MATRIX_HEIGHT) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int y = 0; y < MATRIX_HEIGHT; y++) { + heat[XY(x, y)] = (heat[XY(x, y + 1)] + heat[XY(x, y + 2)] + heat[XY(x, y + 2)]) / 3; + } + + // Step 2. Randomly ignite new 'sparks' of heat + if (random8() < sparking) { + uint8_t xt = random8(MATRIX_CENTRE_X - 2, MATRIX_CENTER_X + 3); + + int xy = XY(xt, MATRIX_HEIGHT - 1); + heat[xy] = qadd8(heat[xy], random8(160, 255)); + } + + // Step 4. Map from heat cells to LED colors + for (int y = 0; y < MATRIX_HEIGHT; y++) { + int xy = XY(x, y); + byte colorIndex = heat[xy]; + + // Recommend that you use values 0-240 rather than + // the usual 0-255, as the last 15 colors will be + // 'wrapping around' from the hot end to the cold end, + // which looks wrong. + colorIndex = scale8(colorIndex, 240); + + // override color 0 to ensure a black background? + if (colorIndex != 0) + // effects.leds[xy] = CRGB::Black; + // else + effects.leds[xy] = effects.ColorFromCurrentPalette(colorIndex); + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseX(4); + + effects.ShowFrame(); + + return 15; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpin.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpin.h new file mode 100644 index 0000000..c3497e7 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpin.h @@ -0,0 +1,100 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternSpin_H + + + +class PatternSpin : public Drawable { +public: + PatternSpin() { + name = (char *)"Spin"; + } + + float degrees = 0; + float radius = 16; + + float speedStart = 1; + float velocityStart = 0.6; + + float maxSpeed = 30; + + float speed = speedStart; + float velocity = velocityStart; + + void start() { + speed = speedStart; + velocity = velocityStart; + degrees = 0; + } + + unsigned int drawFrame() { + effects.DimAll(190); effects.ShowFrame(); + + CRGB color = effects.ColorFromCurrentPalette(speed * 8); + + // start position + int x; + int y; + + // target position + float targetDegrees = degrees + speed; + float targetRadians = radians(targetDegrees); + int targetX = (int) (MATRIX_CENTER_X + radius * cos(targetRadians)); + int targetY = (int) (MATRIX_CENTER_Y - radius * sin(targetRadians)); + + float tempDegrees = degrees; + + do{ + float radians = radians(tempDegrees); + x = (int) (MATRIX_CENTER_X + radius * cos(radians)); + y = (int) (MATRIX_CENTER_Y - radius * sin(radians)); + + effects.drawBackgroundFastLEDPixelCRGB(x, y, color); + effects.drawBackgroundFastLEDPixelCRGB(y, x, color); + + tempDegrees += 1; + if (tempDegrees >= 360) + tempDegrees = 0; + } while (x != targetX || y != targetY); + + degrees += speed; + + // add velocity to the particle each pass around the accelerator + if (degrees >= 360) { + degrees = 0; + speed += velocity; + if (speed <= speedStart) { + speed = speedStart; + velocity *= -1; + } + else if (speed > maxSpeed){ + speed = maxSpeed - velocity; + velocity *= -1; + } + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpiral.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpiral.h new file mode 100644 index 0000000..b8e6f07 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpiral.h @@ -0,0 +1,138 @@ +/* + * Portions of this code are adapted from "Funky Clouds" by Stefan Petrick: + * https://gist.github.com/anonymous/876f908333cd95315c35 + * + * Copyright (c) 2014 Stefan Petrick + * http://www.stefan-petrick.de/wordpress_beta + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSpiral_H +#define PatternSpiral_H + +class PatternSpiral : public Drawable { +private: + // Timer stuff (Oscillators) + struct timer { + unsigned long takt; + unsigned long lastMillis; + unsigned long count; + int delta; + byte up; + byte down; + }; + timer multiTimer[5]; + + int timers = sizeof(multiTimer) / sizeof(multiTimer[0]); + + // counts all variables with different speeds linear up and down + void UpdateTimers() + { + unsigned long now = millis(); + for (int i = 0; i < timers; i++) + { + while (now - multiTimer[i].lastMillis >= multiTimer[i].takt) + { + multiTimer[i].lastMillis += multiTimer[i].takt; + multiTimer[i].count = multiTimer[i].count + multiTimer[i].delta; + if ((multiTimer[i].count == multiTimer[i].up) || (multiTimer[i].count == multiTimer[i].down)) + { + multiTimer[i].delta = -multiTimer[i].delta; + } + } + } + } + +public: + PatternSpiral() { + name = (char *)"Spiral"; + } + + void start() { + // set all counting directions positive for the beginning + for (int i = 0; i < timers; i++) multiTimer[i].delta = 1; + + // set range (up/down), speed (takt=ms between steps) and starting point of all oscillators + + unsigned long now = millis(); + + multiTimer[0].lastMillis = now; + multiTimer[0].takt = 42; //x1 + multiTimer[0].up = MATRIX_WIDTH - 1; + multiTimer[0].down = 0; + multiTimer[0].count = 0; + + multiTimer[1].lastMillis = now; + multiTimer[1].takt = 55; //y1 + multiTimer[1].up = MATRIX_HEIGHT - 1; + multiTimer[1].down = 0; + multiTimer[1].count = 0; + + multiTimer[2].lastMillis = now; + multiTimer[2].takt = 3; //color + multiTimer[2].up = 255; + multiTimer[2].down = 0; + multiTimer[2].count = 0; + + multiTimer[3].lastMillis = now; + multiTimer[3].takt = 71; //x2 + multiTimer[3].up = MATRIX_WIDTH - 1; + multiTimer[3].down = 0; + multiTimer[3].count = 0; + + multiTimer[4].lastMillis = now; + multiTimer[4].takt = 89; //y2 + multiTimer[4].up = MATRIX_HEIGHT - 1; + multiTimer[4].down = 0; + multiTimer[4].count = 0; + } + + unsigned int drawFrame() { + // manage the Oscillators + UpdateTimers(); + + // draw just a line defined by 5 oscillators + effects.BresenhamLine( + multiTimer[3].count, // x1 + multiTimer[4].count, // y1 + multiTimer[0].count, // x2 + multiTimer[1].count, // y2 + multiTimer[2].count); // color + + // manipulate the screen buffer + // with fixed parameters (could be oscillators too) + // Params: center x, y, radius, scale color down + // --> NOTE: Affects always a SQUARE with an odd length + // effects.SpiralStream(15, 15, 10, 128); + + effects.SpiralStream(31, 15, 64, 128); // for 64 pixel wide matrix! + // effects.SpiralStream(47, 15, 10, 128); // for 64 pixel wide matrix! + + // why not several times?! + // effects.SpiralStream(16, 6, 6, 128); + // effects.SpiralStream(10, 24, 10, 128); + + // increase the contrast + effects.DimAll(250); effects.ShowFrame(); + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpiro.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpiro.h new file mode 100644 index 0000000..c41a3c6 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSpiro.h @@ -0,0 +1,112 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSpiro_H + +class PatternSpiro : public Drawable { + private: + byte theta1 = 0; + byte theta2 = 0; + byte hueoffset = 0; + + uint8_t radiusx = MATRIX_WIDTH / 4; + uint8_t radiusy = MATRIX_HEIGHT / 4; + uint8_t minx = MATRIX_CENTER_X - radiusx; + uint8_t maxx = MATRIX_CENTER_X + radiusx + 1; + uint8_t miny = MATRIX_CENTER_Y - radiusy; + uint8_t maxy = MATRIX_CENTER_Y + radiusy + 1; + + uint8_t spirocount = 1; + uint8_t spirooffset = 256 / spirocount; + boolean spiroincrement = true; + + boolean handledChange = false; + + public: + PatternSpiro() { + name = (char *)"Spiro"; + } + + void start(){ + effects.ClearFrame(); + }; + + unsigned int drawFrame() { + blur2d(effects.leds, MATRIX_WIDTH > 255 ? 255 : MATRIX_WIDTH, MATRIX_HEIGHT > 255 ? 255 : MATRIX_HEIGHT, 192); + + boolean change = false; + + for (int i = 0; i < spirocount; i++) { + uint8_t x = mapsin8(theta1 + i * spirooffset, minx, maxx); + uint8_t y = mapcos8(theta1 + i * spirooffset, miny, maxy); + + uint8_t x2 = mapsin8(theta2 + i * spirooffset, x - radiusx, x + radiusx); + uint8_t y2 = mapcos8(theta2 + i * spirooffset, y - radiusy, y + radiusy); + + CRGB color = effects.ColorFromCurrentPalette(hueoffset + i * spirooffset, 128); + effects.leds[XY(x2, y2)] += color; + + if((x2 == MATRIX_CENTER_X && y2 == MATRIX_CENTER_Y) || + (x2 == MATRIX_CENTRE_X && y2 == MATRIX_CENTRE_Y)) change = true; + } + + theta2 += 1; + + EVERY_N_MILLIS(25) { + theta1 += 1; + } + + EVERY_N_MILLIS(100) { + if (change && !handledChange) { + handledChange = true; + + if (spirocount >= MATRIX_WIDTH || spirocount == 1) spiroincrement = !spiroincrement; + + if (spiroincrement) { + if(spirocount >= 4) + spirocount *= 2; + else + spirocount += 1; + } + else { + if(spirocount > 4) + spirocount /= 2; + else + spirocount -= 1; + } + + spirooffset = 256 / spirocount; + } + + if(!change) handledChange = false; + } + + EVERY_N_MILLIS(33) { + hueoffset += 1; + } + + effects.ShowFrame(); + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSwirl.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSwirl.h new file mode 100644 index 0000000..a81ed61 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternSwirl.h @@ -0,0 +1,79 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Portions of this code are adapted from SmartMatrixSwirl by Mark Kriegsman: https://gist.github.com/kriegsman/5adca44e14ad025e6d3b +* https://www.youtube.com/watch?v=bsGBT-50cts +* Copyright (c) 2014 Mark Kriegsman +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternSwirl_H + +class PatternSwirl : public Drawable { + private: + const uint8_t borderWidth = 2; + + public: + PatternSwirl() { + name = (char *)"Swirl"; + } + + void start() { + effects.ClearFrame(); + } + + unsigned int drawFrame() { + // Apply some blurring to whatever's already on the matrix + // Note that we never actually clear the matrix, we just constantly + // blur it repeatedly. Since the blurring is 'lossy', there's + // an automatic trend toward black -- by design. + uint8_t blurAmount = beatsin8(2, 10, 255); + +#if FASTLED_VERSION >= 3001000 + blur2d(effects.leds, MATRIX_WIDTH > 255 ? 255 : MATRIX_WIDTH, MATRIX_HEIGHT > 255 ? 255 : MATRIX_HEIGHT, blurAmount); +#else + effects.DimAll(blurAmount); +#endif + + // Use two out-of-sync sine waves + uint8_t i = beatsin8(256/MATRIX_HEIGHT, borderWidth, MATRIX_WIDTH - borderWidth); + uint8_t j = beatsin8(2048/MATRIX_WIDTH, borderWidth, MATRIX_HEIGHT - borderWidth); + + // Also calculate some reflections + uint8_t ni = (MATRIX_WIDTH - 1) - i; + uint8_t nj = (MATRIX_HEIGHT - 1) - j; + + // The color of each point shifts over time, each at a different speed. + uint16_t ms = millis(); + effects.leds[XY(i, j)] += effects.ColorFromCurrentPalette(ms / 11); + //effects.leds[XY(j, i)] += effects.ColorFromCurrentPalette(ms / 13); // this doesn't work for non-square matrices + effects.leds[XY(ni, nj)] += effects.ColorFromCurrentPalette(ms / 17); + //effects.leds[XY(nj, ni)] += effects.ColorFromCurrentPalette(ms / 29); // this doesn't work for non-square matrices + effects.leds[XY(i, nj)] += effects.ColorFromCurrentPalette(ms / 37); + effects.leds[XY(ni, j)] += effects.ColorFromCurrentPalette(ms / 41); + + + effects.ShowFrame(); + return 0; + + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternTest.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternTest.h new file mode 100644 index 0000000..61e870a --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternTest.h @@ -0,0 +1,20 @@ + +#ifndef PatternTest_H +#define PatternTest_H + +class PatternTest : public Drawable { + private: + + public: + PatternTest() { + name = (char *)"Test Pattern"; + } + + unsigned int drawFrame() { + + dma_display->fillScreen(dma_display->color565(128, 0, 0)); + return 1000; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternWave.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternWave.h new file mode 100644 index 0000000..8246a5f --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/PatternWave.h @@ -0,0 +1,120 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternWave_H +#define PatternWave_H + +class PatternWave : public Drawable { +private: + byte thetaUpdate = 0; + byte thetaUpdateFrequency = 0; + byte theta = 0; + + byte hueUpdate = 0; + byte hueUpdateFrequency = 0; + byte hue = 0; + + byte rotation = 0; + + uint8_t scale = 256 / MATRIX_WIDTH; + + uint8_t maxX = MATRIX_WIDTH - 1; + uint8_t maxY = MATRIX_HEIGHT - 1; + + uint8_t waveCount = 1; + +public: + PatternWave() { + name = (char *)"Wave"; + } + + void start() { + rotation = random(0, 4); + waveCount = random(1, 3); + + } + + unsigned int drawFrame() { + int n = 0; + + switch (rotation) { + case 0: + for (int x = 0; x < MATRIX_WIDTH; x++) { + n = quadwave8(x * 2 + theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(x, n, effects.ColorFromCurrentPalette(x + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(x, maxY - n, effects.ColorFromCurrentPalette(x + hue)); + } + break; + + case 1: + for (int y = 0; y < MATRIX_HEIGHT; y++) { + n = quadwave8(y * 2 + theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(n, y, effects.ColorFromCurrentPalette(y + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(maxX - n, y, effects.ColorFromCurrentPalette(y + hue)); + } + break; + + case 2: + for (int x = 0; x < MATRIX_WIDTH; x++) { + n = quadwave8(x * 2 - theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(x, n, effects.ColorFromCurrentPalette(x + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(x, maxY - n, effects.ColorFromCurrentPalette(x + hue)); + } + break; + + case 3: + for (int y = 0; y < MATRIX_HEIGHT; y++) { + n = quadwave8(y * 2 - theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(n, y, effects.ColorFromCurrentPalette(y + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(maxX - n, y, effects.ColorFromCurrentPalette(y + hue)); + } + break; + } + + effects.DimAll(254); + effects.ShowFrame(); + + if (thetaUpdate >= thetaUpdateFrequency) { + thetaUpdate = 0; + theta++; + } + else { + thetaUpdate++; + } + + if (hueUpdate >= hueUpdateFrequency) { + hueUpdate = 0; + hue++; + } + else { + hueUpdate++; + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Patterns.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Patterns.h new file mode 100644 index 0000000..100a545 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Patterns.h @@ -0,0 +1,299 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Patterns_H +#define Patterns_H + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +#include "Vector.h" +#include "Boid.h" +#include "Attractor.h" + +/* + * Note from mrfaptastic: + * + * Commented out patterns are due to the fact they either didn't work properly with a non-square display, + * or from my personal opinion, are crap. + */ + +#include "PatternTest.h" +//#include "PatternNoiseSmearing.h" // Doesn't seem to work, omitting. +#include "PatternSpiro.h" +#include "PatternRadar.h" +#include "PatternSwirl.h" +#include "PatternPendulumWave.h" +#include "PatternFlowField.h" +#include "PatternIncrementalDrift.h" +//#include "PatternIncrementalDrift2.h" // Doesn't seem to work, omitting. +#include "PatternMunch.h" +#include "PatternElectricMandala.h" +//#include "PatternSpin.h" // Doesn't seem to work, omitting. +#include "PatternSimplexNoise.h" +#include "PatternWave.h" +#include "PatternAttract.h" +//#include "PatternBounce.h" // Doesn't seem to work, omitting. +#include "PatternFlock.h" +#include "PatternInfinity.h" +#include "PatternPlasma.h" +#include "PatternSnake.h" +#include "PatternInvaders.h" +//#include "PatternCube.h" // Doesn't seem to work, omitting. +//#include "PatternFire.h" // Doesn't seem to work, omitting. +#include "PatternLife.h" +#include "PatternMaze.h" +//#include "PatternPulse.h" // Doesn't seem to work, omitting. +//#include "PatternSpark.h" // Doesn't seem to work, omitting. +#include "PatternSpiral.h" + +class Patterns : public Playlist { + private: + PatternTest patternTest; + // PatternRainbowFlag rainbowFlag; // doesn't work + // PatternPaletteSmear paletteSmear; + // PatternMultipleStream multipleStream; // doesn't work + // PatternMultipleStream2 multipleStream2; // doesn't work + // PatternMultipleStream3 multipleStream3; // doesn't work + // PatternMultipleStream4 multipleStream4; // doesn't work + // PatternMultipleStream5 multipleStream5; // doesn't work + // PatternMultipleStream8 multipleStream8; // doesn't work + PatternSpiro spiro; + // PatternRadar radar; + PatternSwirl swirl; + PatternPendulumWave pendulumWave; + PatternFlowField flowField; + PatternIncrementalDrift incrementalDrift; + // PatternIncrementalDrift2 incrementalDrift2; + PatternMunch munch; + PatternElectricMandala electricMandala; + // PatternSpin spin; + PatternSimplexNoise simplexNoise; + PatternWave wave; + PatternAttract attract; + // PatternBounce bounce; + PatternFlock flock; + PatternInfinity infinity; + PatternPlasma plasma; + PatternInvadersSmall invadersSmall; + // PatternInvadersMedium invadersMedium; + // PatternInvadersLarge invadersLarge; + PatternSnake snake; + // PatternCube cube; + // PatternFire fire; + PatternLife life; + PatternMaze maze; + // PatternPulse pulse; + // PatternSpark spark; + PatternSpiral spiral; + + int currentIndex = 0; + Drawable* currentItem; + + int getCurrentIndex() { + return currentIndex; + } + + //const static int PATTERN_COUNT = 37; + + const static int PATTERN_COUNT = 17; + + Drawable* shuffledItems[PATTERN_COUNT]; + + Drawable* items[PATTERN_COUNT] = { + // &patternTest, // ok + &spiro, // cool + // &paletteSmear, // fail + // &multipleStream, // fail + // &multipleStream8,// fail + // &multipleStream5,// fail + // &multipleStream3,// fail + // &radar, // fail + // &multipleStream4, // fail + // &multipleStream2, // fail + &life, // ok + &flowField, + &pendulumWave, //11 ok + + &incrementalDrift, //12 ok + // &incrementalDrift2, // 13 fail + &munch, // 14 ok + &electricMandala, // 15 ok + // &spin, // 16 ok but repetitive + &simplexNoise, // 17 - cool! + // &wave, // 18 ok (can't work with 256+ matrix due to uint8_t vars) + // &rainbowFlag, //20 // fail + &attract, // 21 ok + &swirl, // 22 + // &bounce, // bouncing line crap + &flock, // works + &infinity, // works + &plasma, // works + &invadersSmall, // works ish + // &invadersMedium, // fail + // &invadersLarge, // fail + &snake, // ok + // &cube, // works ish + // &fire, // ok ish + &maze, // ok + // &pulse,// fail + // &spark, // same as fire + &spiral, // ok + }; + + public: + Patterns() { + // add the items to the shuffledItems array + for (int a = 0; a < PATTERN_COUNT; a++) { + shuffledItems[a] = items[a]; + } + + shuffleItems(); + + this->currentItem = items[0]; + this->currentItem->start(); + } + + char* Drawable::name = (char *)"Patterns"; + + void stop() { + if (currentItem) + currentItem->stop(); + } + + void start() { + if (currentItem) + currentItem->start(); + } + + void move(int step) { + currentIndex += step; + + if (currentIndex >= PATTERN_COUNT) currentIndex = 0; + else if (currentIndex < 0) currentIndex = PATTERN_COUNT - 1; + + if (effects.paletteIndex == effects.RandomPaletteIndex) + effects.RandomPalette(); + + moveTo(currentIndex); + + //if (!isTimeAvailable && currentItem == &analogClock) + // move(step); + } + + void moveRandom(int step) { + currentIndex += step; + + if (currentIndex >= PATTERN_COUNT) currentIndex = 0; + else if (currentIndex < 0) currentIndex = PATTERN_COUNT - 1; + + if (effects.paletteIndex == effects.RandomPaletteIndex) + effects.RandomPalette(); + + if (currentItem) + currentItem->stop(); + + currentItem = shuffledItems[currentIndex]; + + if (currentItem) + currentItem->start(); + + // if (!isTimeAvailable && currentItem == &analogClock) + // moveRandom(step); + } + + void shuffleItems() { + for (int a = 0; a < PATTERN_COUNT; a++) + { + int r = random(a, PATTERN_COUNT); + Drawable* temp = shuffledItems[a]; + shuffledItems[a] = shuffledItems[r]; + shuffledItems[r] = temp; + } + } + + + unsigned int drawFrame() { + return currentItem->drawFrame(); + } + + void listPatterns() { + Serial.println(F("{")); + Serial.print(F(" \"count\": ")); + Serial.print(PATTERN_COUNT); + Serial.println(","); + Serial.println(F(" \"results\": [")); + + for (int i = 0; i < PATTERN_COUNT; i++) { + Serial.print(F(" \"")); + Serial.print(i, DEC); + Serial.print(F(": ")); + Serial.print(items[i]->name); + if (i == PATTERN_COUNT - 1) + Serial.println(F("\"")); + else + Serial.println(F("\",")); + } + + Serial.println(" ]"); + Serial.println("}"); + } + + char * getCurrentPatternName() + { + return currentItem->name; + } + + void moveTo(int index) { + if (currentItem) + currentItem->stop(); + + currentIndex = index; + + currentItem = items[currentIndex]; + + if (currentItem) + currentItem->start(); + } + + bool setPattern(String name) { + for (int i = 0; i < PATTERN_COUNT; i++) { + if (name.compareTo(items[i]->name) == 0) { + moveTo(i); + return true; + } + } + + return false; + } + + + bool setPattern(int index) { + if (index >= PATTERN_COUNT || index < 0) + return false; + + moveTo(index); + + return true; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Playlist.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Playlist.h new file mode 100644 index 0000000..29c0c87 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Playlist.h @@ -0,0 +1,39 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Playlist_H +#define Playlist_H + +class Playlist : public Drawable { +public: + virtual bool isPlaylist() { + return true; + } + + boolean isCurrentItemFinished = true; + + virtual void move(int step) = 0; + virtual void moveRandom(int step) = 0; + virtual int getCurrentIndex(); +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/README.md new file mode 100644 index 0000000..2344367 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/README.md @@ -0,0 +1,6 @@ +A port of Aurora visualisations +====== + +Not all of the visualisations have been ported. About 17 of 37 work, or have been included as I think they look best. + +Original source: https://github.com/pixelmatix/aurora \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Vector.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Vector.h new file mode 100644 index 0000000..8acbadc --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/AuroraDemo/Vector.h @@ -0,0 +1,169 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Vector_H +#define Vector_H + +template +class Vector2 { +public: + T x, y; + + Vector2() :x(0), y(0) {} + Vector2(T x, T y) : x(x), y(y) {} + Vector2(const Vector2& v) : x(v.x), y(v.y) {} + + Vector2& operator=(const Vector2& v) { + x = v.x; + y = v.y; + return *this; + } + + bool isEmpty() { + return x == 0 && y == 0; + } + + bool operator==(Vector2& v) { + return x == v.x && y == v.y; + } + + bool operator!=(Vector2& v) { + return !(x == y); + } + + Vector2 operator+(Vector2& v) { + return Vector2(x + v.x, y + v.y); + } + Vector2 operator-(Vector2& v) { + return Vector2(x - v.x, y - v.y); + } + + Vector2& operator+=(Vector2& v) { + x += v.x; + y += v.y; + return *this; + } + Vector2& operator-=(Vector2& v) { + x -= v.x; + y -= v.y; + return *this; + } + + Vector2 operator+(double s) { + return Vector2(x + s, y + s); + } + Vector2 operator-(double s) { + return Vector2(x - s, y - s); + } + Vector2 operator*(double s) { + return Vector2(x * s, y * s); + } + Vector2 operator/(double s) { + return Vector2(x / s, y / s); + } + + Vector2& operator+=(double s) { + x += s; + y += s; + return *this; + } + Vector2& operator-=(double s) { + x -= s; + y -= s; + return *this; + } + Vector2& operator*=(double s) { + x *= s; + y *= s; + return *this; + } + Vector2& operator/=(double s) { + x /= s; + y /= s; + return *this; + } + + void set(T x, T y) { + this->x = x; + this->y = y; + } + + void rotate(double deg) { + double theta = deg / 180.0 * M_PI; + double c = cos(theta); + double s = sin(theta); + double tx = x * c - y * s; + double ty = x * s + y * c; + x = tx; + y = ty; + } + + Vector2& normalize() { + if (length() == 0) return *this; + *this *= (1.0 / length()); + return *this; + } + + float dist(Vector2 v) const { + Vector2 d(v.x - x, v.y - y); + return d.length(); + } + float length() const { + return sqrt(x * x + y * y); + } + + float mag() const { + return length(); + } + + float magSq() { + return (x * x + y * y); + } + + void truncate(double length) { + double angle = atan2f(y, x); + x = length * cos(angle); + y = length * sin(angle); + } + + Vector2 ortho() const { + return Vector2(y, -x); + } + + static float dot(Vector2 v1, Vector2 v2) { + return v1.x * v2.x + v1.y * v2.y; + } + static float cross(Vector2 v1, Vector2 v2) { + return (v1.x * v2.y) - (v1.y * v2.x); + } + + void limit(float max) { + if (magSq() > max*max) { + normalize(); + *this *= max; + } + } +}; + +typedef Vector2 PVector; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/BitmapIcons.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/BitmapIcons.ino new file mode 100644 index 0000000..ab1b6e4 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/BitmapIcons.ino @@ -0,0 +1,204 @@ +#include +#include +#include "Dhole_weather_icons32px.h" + +/*--------------------- DEBUG -------------------------*/ +#define Sprintln(a) (Serial.println(a)) +#define SprintlnDEC(a, x) (Serial.println(a, x)) + +#define Sprint(a) (Serial.print(a)) +#define SprintDEC(a, x) (Serial.print(a, x)) + + +/*--------------------- RGB DISPLAY PINS -------------------------*/ +#define R1_PIN 25 +#define G1_PIN 26 +#define B1_PIN 27 +#define R2_PIN 14 +#define G2_PIN 12 +#define B2_PIN 13 +#define A_PIN 23 +#define B_PIN 19 // Changed from library default +#define C_PIN 5 +#define D_PIN 17 +#define E_PIN -1 +#define LAT_PIN 4 +#define OE_PIN 15 +#define CLK_PIN 16 + + +/*--------------------- MATRIX LILBRARY CONFIG -------------------------*/ +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +MatrixPanel_I2S_DMA *dma_display = nullptr; + +// Module configuration +HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length +); + +/* +//Another way of creating config structure +//Custom pin mapping for all pins +HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; +HUB75_I2S_CFG mxconfig( + 64, // width + 64, // height + 4, // chain length + _pins, // pin mapping + HUB75_I2S_CFG::FM6126A // driver chip +); + +*/ + + +//mxconfig.gpio.e = -1; // Assign a pin if you have a 64x64 panel +//mxconfig.clkphase = false; // Change this if you have issues with ghosting. +//mxconfig.driver = HUB75_I2S_CFG::FM6126A; // Change this according to your pane. + + +/* + * Wifi Logo, generated with the following steps: + * + * Python and Paint.Net needs to be installed. + * + * 1. SAVE BITMAP AS 1BIT COLOUR in paint.net + * 2. Run: bmp2hex.py -i -x + * 3. Copy paste output into sketch. + * + */ + +const char wifi_image1bit[] PROGMEM = { + 0x00,0x00,0x00,0xf8,0x1f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0xf0,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x1f, + 0x00,0x00,0x00,0x00,0xfe,0x07,0xe0,0x7f,0x00,0x00,0x00,0x80,0xff,0x00,0x00, + 0xff,0x01,0x00,0x00,0xc0,0x1f,0x00,0x00,0xf8,0x03,0x00,0x00,0xe0,0x0f,0x00, + 0x00,0xf0,0x07,0x00,0x00,0xf0,0x03,0xf0,0x0f,0xc0,0x0f,0x00,0x00,0xe0,0x01, + 0xff,0xff,0x80,0x07,0x00,0x00,0xc0,0xc0,0xff,0xff,0x03,0x03,0x00,0x00,0x00, + 0xe0,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xf8,0x0f,0xf0,0x1f,0x00,0x00,0x00, + 0x00,0xfc,0x01,0x80,0x3f,0x00,0x00,0x00,0x00,0x7c,0x00,0x00,0x3e,0x00,0x00, + 0x00,0x00,0x38,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x10,0xe0,0x07,0x08,0x00, + 0x00,0x00,0x00,0x00,0xfc,0x3f,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0x7f,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xf8, + 0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0xc0,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0xe0,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x80,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00 }; + + +void drawXbm565(int x, int y, int width, int height, const char *xbm, uint16_t color = 0xffff) +{ + if (width % 8 != 0) { + width = ((width / 8) + 1) * 8; + } + for (int i = 0; i < width * height / 8; i++ ) { + unsigned char charColumn = pgm_read_byte(xbm + i); + for (int j = 0; j < 8; j++) { + int targetX = (i * 8 + j) % width + x; + int targetY = (8 * i / (width)) + y; + if (bitRead(charColumn, j)) { + dma_display->drawPixel(targetX, targetY, color); + } + } + } +} + +/* Bitmaps */ +int current_icon = 0; +static int num_icons = 22; + +static char icon_name[22][30] = { +"cloud_moon_bits", +"cloud_sun_bits", +"clouds_bits", +"cloud_wind_moon_bits", +"cloud_wind_sun_bits", +"cloud_wind_bits", +"cloud_bits", +"lightning_bits", +"moon_bits", +"rain0_sun_bits", +"rain0_bits", +"rain1_moon_bits", +"rain1_sun_bits", +"rain1_bits", +"rain2_bits", +"rain_lightning_bits", +"rain_snow_bits", +"snow_moon_bits", +"snow_sun_bits", +"snow_bits", +"sun_bits", +"wind_bits" }; + +static char *icon_bits[22] = { cloud_moon_bits, +cloud_sun_bits, +clouds_bits, +cloud_wind_moon_bits, +cloud_wind_sun_bits, +cloud_wind_bits, +cloud_bits, +lightning_bits, +moon_bits, +rain0_sun_bits, +rain0_bits, +rain1_moon_bits, +rain1_sun_bits, +rain1_bits, +rain2_bits, +rain_lightning_bits, +rain_snow_bits, +snow_moon_bits, +snow_sun_bits, +snow_bits, +sun_bits, +wind_bits}; + + + +void setup() { + + // put your setup code here, to run once: + delay(1000); Serial.begin(115200); delay(200); + + + /************** DISPLAY **************/ + Sprintln("...Starting Display"); + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->setBrightness8(90); //0-255 + dma_display->clearScreen(); + + dma_display->fillScreen(dma_display->color444(0, 1, 0)); + + // Fade a Red Wifi Logo In + for (int r=0; r < 255; r++ ) + { + drawXbm565(0,0,64,32, wifi_image1bit, dma_display->color565(r,0,0)); + delay(10); + } + + delay(2000); + dma_display->clearScreen(); +} + + +void loop() { + + // Loop through Weather Icons + Serial.print("Showing icon "); + Serial.println(icon_name[current_icon]); + drawXbm565(0,0, 32, 32, icon_bits[current_icon]); + + current_icon = (current_icon +1 ) % num_icons; + delay(2000); + dma_display->clearScreen(); + +} \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/Dhole_weather_icons32px.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/Dhole_weather_icons32px.h new file mode 100644 index 0000000..5fb8b3b --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/Dhole_weather_icons32px.h @@ -0,0 +1,308 @@ +#define cloud_moon_width 32 +#define cloud_moon_height 32 +static char cloud_moon_bits[] = { + 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x7C, 0x00, + 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x67, 0x00, + 0x00, 0x00, 0xE3, 0x00, 0x00, 0x00, 0xC3, 0x01, 0x00, 0xF0, 0x87, 0xC3, + 0x00, 0xFC, 0x1F, 0xFF, 0x00, 0x1E, 0x3C, 0xFE, 0x00, 0x06, 0x30, 0x60, + 0x00, 0x07, 0xF0, 0x79, 0x00, 0x03, 0xE0, 0x3F, 0xE0, 0x03, 0x00, 0x0F, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x07, 0x38, 0x00, 0x80, 0x03, + 0xF0, 0xFF, 0xFF, 0x01, 0xE0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define cloud_sun_width 32 +#define cloud_sun_height 32 +static char cloud_sun_bits[] = { + 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x80, 0xC1, 0x60, + 0x00, 0x80, 0x03, 0x70, 0x00, 0x00, 0xF3, 0x33, 0x00, 0x00, 0xF8, 0x07, + 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0xF0, 0x07, 0x18, + 0x00, 0xFC, 0x1F, 0xD8, 0x00, 0x1E, 0x3C, 0xD8, 0x00, 0x06, 0x30, 0x18, + 0x00, 0x07, 0xF0, 0x1D, 0x00, 0x03, 0xE0, 0x0F, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x07, 0x38, 0x00, 0x80, 0x03, + 0xF0, 0xFF, 0xFF, 0x01, 0xE0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define clouds_width 32 +#define clouds_height 32 +static char clouds_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0xC0, 0xFF, 0x01, + 0x00, 0xE0, 0xC1, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0xF0, 0x07, 0x1F, + 0x00, 0xFC, 0x1F, 0x3E, 0x00, 0x1E, 0x3C, 0x70, 0x00, 0x06, 0x30, 0xE0, + 0x00, 0x07, 0xF0, 0xC1, 0x00, 0x03, 0xE0, 0xC3, 0xE0, 0x03, 0x00, 0xC7, + 0xF0, 0x01, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xCC, 0x1C, 0x00, 0x00, 0xEC, + 0x0C, 0x00, 0x00, 0x6C, 0x0C, 0x00, 0x00, 0x2C, 0x0C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x07, 0x38, 0x00, 0x80, 0x03, + 0xF0, 0xFF, 0xFF, 0x01, 0xE0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define cloud_wind_moon_width 32 +#define cloud_wind_moon_height 32 +static char cloud_wind_moon_bits[] = { + 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x7C, 0x00, + 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x67, 0x00, + 0x00, 0x00, 0xE3, 0x00, 0x00, 0x00, 0xC3, 0x01, 0x00, 0xF0, 0x87, 0xC3, + 0x00, 0xFC, 0x1F, 0xFF, 0x00, 0x1E, 0x3C, 0xFE, 0x00, 0x06, 0x30, 0x60, + 0x00, 0x07, 0xF0, 0x79, 0x00, 0x03, 0xE0, 0x3F, 0xE0, 0x03, 0x00, 0x0F, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, + 0xFC, 0xFF, 0x3F, 0x0E, 0xFC, 0xFF, 0x3F, 0x07, 0x00, 0x00, 0x80, 0x03, + 0xFF, 0xFF, 0xEF, 0x01, 0xFF, 0xFF, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFC, 0xFF, 0x3F, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define cloud_wind_sun_width 32 +#define cloud_wind_sun_height 32 +static char cloud_wind_sun_bits[] = { + 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x80, 0xC1, 0x60, + 0x00, 0x80, 0x03, 0x70, 0x00, 0x00, 0xF3, 0x33, 0x00, 0x00, 0xF8, 0x07, + 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0xF0, 0x07, 0x18, + 0x00, 0xFC, 0x1F, 0xD8, 0x00, 0x1E, 0x3C, 0xD8, 0x00, 0x06, 0x30, 0x18, + 0x00, 0x07, 0xF0, 0x1D, 0x00, 0x03, 0xE0, 0x0F, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, + 0xFC, 0xFF, 0x3F, 0x0E, 0xFC, 0xFF, 0x3F, 0x07, 0x00, 0x00, 0x80, 0x03, + 0xFF, 0xFF, 0xEF, 0x01, 0xFF, 0xFF, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFC, 0xFF, 0x3F, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define cloud_wind_width 32 +#define cloud_wind_height 32 +static char cloud_wind_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, + 0xFC, 0xFF, 0x3F, 0x0E, 0xFC, 0xFF, 0x3F, 0x07, 0x00, 0x00, 0x80, 0x03, + 0xFF, 0xFF, 0xEF, 0x01, 0xFF, 0xFF, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFC, 0xFF, 0x3F, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define cloud_width 32 +#define cloud_height 32 +static char cloud_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x07, 0x38, 0x00, 0x80, 0x03, + 0xF0, 0xFF, 0xFF, 0x01, 0xE0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define lightning_width 32 +#define lightning_height 32 +static char lightning_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0xC0, 0x01, 0x0C, + 0x0C, 0xC0, 0x01, 0x0E, 0x1C, 0xC0, 0x00, 0x07, 0x38, 0xE0, 0x80, 0x03, + 0xF0, 0x6F, 0xFC, 0x01, 0xE0, 0xEF, 0xFD, 0x00, 0x00, 0x80, 0x01, 0x00, + 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define moon_width 32 +#define moon_height 32 +static char moon_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x78, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, + 0x80, 0x1B, 0x00, 0x00, 0xC0, 0x19, 0x00, 0x00, 0xE0, 0x1C, 0x00, 0x00, + 0x60, 0x0C, 0x00, 0x00, 0x70, 0x0C, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, + 0x30, 0x0C, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, + 0x18, 0x38, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x18, 0x70, 0x00, 0x00, + 0x38, 0xE0, 0x01, 0x18, 0x30, 0xC0, 0x87, 0x1F, 0x70, 0x00, 0xFF, 0x1F, + 0x60, 0x00, 0xFC, 0x0C, 0xE0, 0x00, 0x00, 0x0E, 0xC0, 0x01, 0x00, 0x07, + 0x80, 0x03, 0x80, 0x03, 0x00, 0x0F, 0xE0, 0x01, 0x00, 0x3E, 0xFC, 0x00, + 0x00, 0xF8, 0x3F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain0_sun_width 32 +#define rain0_sun_height 32 +static char rain0_sun_bits[] = { + 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x80, 0xC1, 0x60, + 0x00, 0x80, 0x03, 0x70, 0x00, 0x00, 0xF3, 0x33, 0x00, 0x00, 0xF8, 0x07, + 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0xF0, 0x07, 0x18, + 0x00, 0xFC, 0x1F, 0xD8, 0x00, 0x1E, 0x3C, 0xD8, 0x00, 0x06, 0x30, 0x18, + 0x00, 0x07, 0xF0, 0x1D, 0x00, 0x03, 0xE0, 0x0F, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x60, 0x06, 0x0C, + 0x0C, 0x60, 0x06, 0x0E, 0x1C, 0x70, 0x07, 0x07, 0x38, 0x30, 0x83, 0x03, + 0xF0, 0x33, 0xFB, 0x01, 0xE0, 0x01, 0xF8, 0x00, 0x00, 0xCC, 0x00, 0x00, + 0x00, 0xCC, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain0_width 32 +#define rain0_height 32 +static char rain0_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x60, 0x06, 0x0C, + 0x0C, 0x60, 0x06, 0x0E, 0x1C, 0x70, 0x07, 0x07, 0x38, 0x30, 0x83, 0x03, + 0xF0, 0x33, 0xFB, 0x01, 0xE0, 0x01, 0xF8, 0x00, 0x00, 0xCC, 0x00, 0x00, + 0x00, 0xCC, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain1_moon_width 32 +#define rain1_moon_height 32 +static char rain1_moon_bits[] = { + 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x7C, 0x00, + 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x67, 0x00, + 0x00, 0x00, 0xE3, 0x00, 0x00, 0x00, 0xC3, 0x01, 0x00, 0xF0, 0x87, 0xC3, + 0x00, 0xFC, 0x1F, 0xFF, 0x00, 0x1E, 0x3C, 0xFE, 0x00, 0x06, 0x30, 0x60, + 0x00, 0x07, 0xF0, 0x79, 0x00, 0x03, 0xE0, 0x3F, 0xE0, 0x03, 0x00, 0x0F, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x60, 0x06, 0x0C, + 0x0C, 0x60, 0x06, 0x0E, 0x1C, 0x70, 0x07, 0x07, 0x38, 0x30, 0x83, 0x03, + 0xF0, 0xBB, 0xFB, 0x01, 0xE0, 0x99, 0xF9, 0x00, 0x00, 0xDC, 0x01, 0x00, + 0x00, 0xCC, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain1_sun_width 32 +#define rain1_sun_height 32 +static char rain1_sun_bits[] = { + 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x80, 0xC1, 0x60, + 0x00, 0x80, 0x03, 0x70, 0x00, 0x00, 0xF3, 0x33, 0x00, 0x00, 0xF8, 0x07, + 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0xF0, 0x07, 0x18, + 0x00, 0xFC, 0x1F, 0xD8, 0x00, 0x1E, 0x3C, 0xD8, 0x00, 0x06, 0x30, 0x18, + 0x00, 0x07, 0xF0, 0x1D, 0x00, 0x03, 0xE0, 0x0F, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x60, 0x06, 0x0C, + 0x0C, 0x60, 0x06, 0x0E, 0x1C, 0x70, 0x07, 0x07, 0x38, 0x30, 0x83, 0x03, + 0xF0, 0xBB, 0xFB, 0x01, 0xE0, 0x99, 0xF9, 0x00, 0x00, 0xDC, 0x01, 0x00, + 0x00, 0xCC, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain1_width 32 +#define rain1_height 32 +static char rain1_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x60, 0x06, 0x0C, + 0x0C, 0x60, 0x06, 0x0E, 0x1C, 0x70, 0x07, 0x07, 0x38, 0x30, 0x83, 0x03, + 0xF0, 0xBB, 0xFB, 0x01, 0xE0, 0x99, 0xF9, 0x00, 0x00, 0xDC, 0x01, 0x00, + 0x00, 0xCC, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain2_width 32 +#define rain2_height 32 +static char rain2_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x66, 0x66, 0x0C, + 0x0C, 0x66, 0x66, 0x0E, 0x1C, 0x77, 0x77, 0x07, 0x38, 0x33, 0x33, 0x03, + 0xB0, 0xBB, 0xBB, 0x01, 0x80, 0x99, 0x99, 0x00, 0xC0, 0xDD, 0x1D, 0x00, + 0xC0, 0xCC, 0x0C, 0x00, 0xC0, 0xEE, 0x0C, 0x00, 0x00, 0x66, 0x00, 0x00, + 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain_lightning_width 32 +#define rain_lightning_height 32 +static char rain_lightning_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0xCC, 0x1C, 0x0C, + 0x0C, 0xCC, 0x1C, 0x0E, 0x1C, 0xEE, 0x0C, 0x07, 0x38, 0x66, 0x8E, 0x03, + 0x70, 0x77, 0xC6, 0x01, 0x20, 0x33, 0xDE, 0x00, 0x80, 0x3B, 0x18, 0x00, + 0x80, 0x19, 0x0C, 0x00, 0x80, 0x1D, 0x0C, 0x00, 0x00, 0x0C, 0x04, 0x00, + 0x00, 0x0C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define rain_snow_width 32 +#define rain_snow_height 32 +static char rain_snow_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0xCC, 0x08, 0x0C, + 0x0C, 0xCC, 0x1C, 0x0E, 0x1C, 0xEE, 0x36, 0x07, 0x38, 0x66, 0x9C, 0x03, + 0x70, 0x77, 0xC8, 0x01, 0x20, 0x33, 0xE2, 0x00, 0x80, 0x3B, 0x07, 0x00, + 0x80, 0x99, 0x0D, 0x00, 0x80, 0x1D, 0x07, 0x00, 0x00, 0x0C, 0x02, 0x00, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define snow_moon_width 32 +#define snow_moon_height 32 +static char snow_moon_bits[] = { + 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x7C, 0x00, + 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x67, 0x00, + 0x00, 0x00, 0xE3, 0x00, 0x00, 0x00, 0xC3, 0x01, 0x00, 0xF0, 0x87, 0xC3, + 0x00, 0xFC, 0x1F, 0xFF, 0x00, 0x1E, 0x3C, 0xFE, 0x00, 0x06, 0x30, 0x60, + 0x00, 0x07, 0xF0, 0x79, 0x00, 0x03, 0xE0, 0x3F, 0xE0, 0x03, 0x00, 0x0F, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0E, 0x1C, 0x40, 0x00, 0x07, 0x38, 0xE0, 0x80, 0x03, + 0xF0, 0xB1, 0xF1, 0x01, 0xE0, 0xE0, 0xE0, 0x00, 0x00, 0x44, 0x04, 0x00, + 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x1B, 0x1B, 0x00, 0x00, 0x0E, 0x0E, 0x00, + 0x00, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define snow_sun_width 32 +#define snow_sun_height 32 +static char snow_sun_bits[] = { + 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x80, 0xC1, 0x60, + 0x00, 0x80, 0x03, 0x70, 0x00, 0x00, 0xF3, 0x33, 0x00, 0x00, 0xF8, 0x07, + 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0xF0, 0x07, 0x18, + 0x00, 0xFC, 0x1F, 0xD8, 0x00, 0x1E, 0x3C, 0xD8, 0x00, 0x06, 0x30, 0x18, + 0x00, 0x07, 0xF0, 0x1D, 0x00, 0x03, 0xE0, 0x0F, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0E, 0x1C, 0x40, 0x00, 0x07, 0x38, 0xE0, 0x80, 0x03, + 0xF0, 0xB1, 0xF1, 0x01, 0xE0, 0xE0, 0xE0, 0x00, 0x00, 0x44, 0x04, 0x00, + 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x1B, 0x1B, 0x00, 0x00, 0x0E, 0x0E, 0x00, + 0x00, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define snow_width 32 +#define snow_height 32 +static char snow_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x06, 0x30, 0x00, + 0x00, 0x07, 0xF0, 0x01, 0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0x00, 0x07, + 0xF0, 0x01, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, + 0x0C, 0x00, 0x00, 0x0E, 0x1C, 0x40, 0x00, 0x07, 0x38, 0xE0, 0x80, 0x03, + 0xF0, 0xB1, 0xF1, 0x01, 0xE0, 0xE0, 0xE0, 0x00, 0x00, 0x44, 0x04, 0x00, + 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x1B, 0x1B, 0x00, 0x00, 0x0E, 0x0E, 0x00, + 0x00, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define sun_width 32 +#define sun_height 32 +static char sun_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x83, 0xC1, 0x00, + 0x00, 0x07, 0xE0, 0x00, 0x00, 0xE6, 0x67, 0x00, 0x00, 0xF0, 0x0F, 0x00, + 0x00, 0x38, 0x1C, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x0C, 0x30, 0x00, + 0xF0, 0x0D, 0xB0, 0x0F, 0xF0, 0x0D, 0xB0, 0x0F, 0x00, 0x0C, 0x30, 0x00, + 0x00, 0x1C, 0x38, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x00, 0xF0, 0x0F, 0x00, + 0x00, 0xE6, 0x67, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x83, 0xC1, 0x00, + 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +#define wind_width 32 +#define wind_height 32 +static char wind_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x01, + 0xF0, 0xFF, 0xFF, 0x19, 0xF0, 0xFF, 0xFF, 0x3C, 0x00, 0x00, 0x00, 0x30, + 0xFC, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0xF0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/README.md new file mode 100644 index 0000000..155365e --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/README.md @@ -0,0 +1,13 @@ +# Xbm Bitmap example +## Requirements +* To generate the required Xbm data to be copied into the Sketch. Have python and [paint.net](https://www.getpaint.net/) installed. +* Bitmap should match the resolution of your display configuration. + +## Instructions + 1. SAVE BITMAP AS 1BIT COLOUR in paint.net + 1. Run: bmp2hex.py -i -x (e.g. "bmp2hex.py -i -x WiFi1bit.bmp") + 1. Copy paste output into sketch. + + ![bmp2hex usage screenshot](screenshot.jpg) + + diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/WiFi1bit.bmp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/WiFi1bit.bmp new file mode 100644 index 0000000..2bace87 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/WiFi1bit.bmp differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/bmp2hex.py b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/bmp2hex.py new file mode 100644 index 0000000..49e1c65 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/bmp2hex.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python + +##@file bmp2hex.py +# @ingroup util +# A script for converting a 1-bit bitmap to HEX for use in an Arduino sketch. +# +# The BMP format is well publicized. The byte order of the actual bitmap is a +# little unusual. The image is stored bottom to top, left to right. In addition, +# The pixel rows are rounded to DWORDS which are 4 bytes long. SO, to convert this +# to left to right, top to bottom, no byte padding. We have to do some calculations +# as we loop through the rows and bytes of the image. See below for more +# +# Usage: +# >>>bmp2hex.py [-i] [-r] [-n] [-d] [-x] [-w ] [-b ] +# +# @param infile The file to convert. +# @param tablename The name of the table to create +# @param raw "-r", bitmap written as raw table [optional] +# @param invert "-i", to invert image pixel colors [optional] +# @param tablewidth "-w , The number of characters for each row of the output table [optional] +# @param sizebytes "-b , Bytes = 0, 1, or 2. 0 = auto. 1 = 1-byte for sizes. 2 = 2-byte sizes (big endian) [optional] +# @param named "-n", use a names structure [optional] +## @param double "-d", use double bytes rather than single ones [optional] +# @param xbm "-x", use XBM format (bits reversed in byte) [optional] +# @param version "-v", returns version number +# +# @author Robert Gallup 2016-02 +# +# Author: Robert Gallup (bg@robertgallup.com) +# License: MIT Opensource License +# +# Copyright 2016-2018 Robert Gallup +# + +import sys, array, os, textwrap, math, random, argparse + +class DEFAULTS(object): + STRUCTURE_NAME = 'GFXMeta' + VERSION = '2.3.4' + +def main (): + + # Default parameters + infile = "" + tablename = "" + tablewidth = 16 + sizebytes = 0 + invert = False + raw = False + named = False + double = False + xbm = False + version = False + + # Set up parser and handle arguments + parser = argparse.ArgumentParser() + # parser.add_argument ("infile", help="The BMP file(s) to convert", type=argparse.FileType('r'), nargs='+', default=['-']) + parser.add_argument ("infile", help="The BMP file(s) to convert", type=argparse.FileType('r'), nargs='*', default=['-']) + parser.add_argument ("-r", "--raw", help="Outputs all data in raw table format", action="store_true") + parser.add_argument ("-i", "--invert", help="Inverts bitmap pixels", action="store_true") + parser.add_argument ("-w", "--width", help="Output table width in hex bytes [default: 16]", type=int) + parser.add_argument ("-b", "--bytes", help="Byte width of BMP sizes: 0=auto, 1, or 2 (big endian) [default: 0]", type=int) + parser.add_argument ("-n", "--named", help="Uses named structure (" + DEFAULTS.STRUCTURE_NAME + ") for data", action="store_true") +# parser.add_argument ("-d", "--double", help="Defines data in 'words' rather than bytes", action="store_true") + parser.add_argument ("-x", "--xbm", help="Uses XBM bit order (low order bit is first pixel of byte)", action="store_true") + parser.add_argument ("-v", "--version", help="Returns the current bmp2hex version", action="store_true") + args = parser.parse_args() + + # Required arguments + infile = args.infile + + # Options + if args.raw: + raw = args.raw + if args.invert: + invert = args.invert + if args.width: + tablewidth = args.width + if args.bytes: + sizebytes = args.bytes % 3 + if args.named: + named = args.named + # if args.double: + # double = args.double + double = False + if args.xbm: + xbm = args.xbm + if args.version: + print ('// bmp2hex version ' + DEFAULTS.VERSION) + + # Output named structure, if requested + if (named): + print ('struct ' + DEFAULTS.STRUCTURE_NAME + ' {') + print (' unsigned int width;') + print (' unsigned int height;') + print (' unsigned int bitDepth;') + print (' int baseline;') + print (' ' + getDoubleType(double)[0] + 'pixel_data;') + print ('};') + print ('') + + # Do the work + for f in args.infile: + if f == '-': + sys.exit() + bmp2hex(f.name, tablewidth, sizebytes, invert, raw, named, double, xbm) + +# Utility function. Return a long int from array (little endian) +def getLONG(a, n): + return (a[n+3] * (2**24)) + (a[n+2] * (2**16)) + (a[n+1] * (2**8)) + (a[n]) + +# Utility function. Return an int from array (little endian) +def getINT(a, n): + return ((a[n+1] * (2**8)) + (a[n])) + +# Reverses pixels in byte +def reflect(a): + r = 0 + for i in range(8): + r <<= 1 + r |= (a & 0x01) + a >>= 1 + return (r) + +# Returns as a tuple, the data type and length for double versus short data types +def getDoubleType (d): + if d: + dType = 'uint16_t' + ' *' + dLen = 2 + else: + dType = 'uint8_t' + ' *' + dLen = 1 + + return (dType, dLen) + + +# Main conversion function +def bmp2hex(infile, tablewidth, sizebytes, invert, raw, named, double, xbm): + + # Set up some variables to handle the "-d" option + (pixelDataType, dataByteLength) = getDoubleType(double) + + # Set the table name to the uppercase root of the file name + tablename = os.path.splitext(infile)[0].upper() + + # Convert tablewidth to characters from hex bytes + tablewidth = int(tablewidth) * 6 + + # Initialize output buffer + outstring = '' + + # Open File + fin = open(os.path.expanduser(infile), "rb") + uint8_tstoread = os.path.getsize(os.path.expanduser(infile)) + valuesfromfile = array.array('B') + try: + valuesfromfile.fromfile(fin, uint8_tstoread) + finally: + fin.close() + + # Get bytes from file + values=valuesfromfile.tolist() + + # Exit if it's not a Windows BMP + if ((values[0] != 0x42) or (values[1] != 0x4D)): + sys.exit ("Error: Unsupported BMP format. Make sure your file is a Windows BMP.") + + # Calculate width, height + dataOffset = getLONG(values, 10) # Offset to image data + pixelWidth = getLONG(values, 18) # Width of image + pixelHeight = getLONG(values, 22) # Height of image + bitDepth = getINT (values, 28) # Bits per pixel + dataSize = getLONG(values, 34) # Size of raw data + + # Calculate line width in bytes and padded byte width (each row is padded to 4-byte multiples) + byteWidth = int(math.ceil(float(pixelWidth * bitDepth)/8.0)) + paddedWidth = int(math.ceil(float(byteWidth)/4.0)*4.0) + + # For auto (sizebytes = 0), set sizebytes to 1 or 2, depending on size of the bitmap + if (sizebytes==0): + if (pixelWidth>255) or (pixelHeight>255): + sizebytes = 2 + else: + sizebytes = 1 + + # The invert byte is set based on the invert command line flag (but, the logic is reversed for 1-bit files) + invertbyte = 0xFF if invert else 0x00 + if (bitDepth == 1): + invertbyte = invertbyte ^ 0xFF + + # Output the hex table declaration + # With "raw" output, output just an array of chars + if (raw): + # Output the data declaration + print ('PROGMEM unsigned char const ' + tablename + ' [] = {') + + # Output the size of the BMP + if (not (sizebytes%2)): + print ("{0:#04X}".format((pixelWidth>>8) & 0xFF) + ", " + "{0:#04X}".format(pixelWidth & 0xFF) + ", " + \ + "{0:#04X}".format((pixelHeight>>8) & 0xFF) + ", " + "{0:#04X}".format(pixelHeight & 0xFF) + ",") + else: + print ("{0:#04X}".format(pixelWidth & 0xFF) + ", " + "{0:#04X}".format(pixelHeight & 0xFF) + ",") + + elif (named): + print ('PROGMEM ' + getDoubleType(double)[0] + ' const ' + tablename + '_PIXELS[] = {') + + elif (xbm): + print ('#define ' + tablename + '_width ' + str(pixelWidth)) + print ('#define ' + tablename + '_height ' + str(pixelHeight)) + print ('PROGMEM ' + getDoubleType(double)[0] + ' const ' + tablename + '_bits[] = {') + + else: + print ('PROGMEM const struct {') + print (' unsigned int width;') + print (' unsigned int height;') + print (' unsigned int bitDepth;') + print (' ' + pixelDataType + 'pixel_data[{0}];'.format(byteWidth * pixelHeight / dataByteLength)) + print ('} ' + tablename + ' = {') + print ('{0}, {1}, {2}, {{'.format(pixelWidth, pixelHeight, bitDepth)) + + # Generate HEX bytes for pixel data in output buffer + try: + for i in range(pixelHeight): + for j in range (byteWidth): + ndx = dataOffset + ((pixelHeight-1-i) * paddedWidth) + j + v = values[ndx] ^ invertbyte + if (xbm): + v = reflect(v) + # print ("{0:#04x}".format(v)) + outstring += "{0:#04x}".format(v) + ", " + + # Wrap the output buffer. Print. Then, finish. + finally: + outstring = textwrap.fill(outstring[:-2], tablewidth) + print (outstring) + + if (named): + print ('};') + print (DEFAULTS.STRUCTURE_NAME + ' const ' + tablename + ' = {{{0}, {1}, {2}, 0, '.format(pixelWidth, pixelHeight, bitDepth) + \ + pixelDataType + tablename + "_PIXELS};\n\n") + else: + if (not (raw or xbm)): + print ("}") + print ("};") + + +# Only run if launched from commandline +if __name__ == '__main__': main() \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/screenshot.jpg b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/screenshot.jpg new file mode 100644 index 0000000..1148260 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/BitmapIcons/screenshot.jpg differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanels/ChainedPanels.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanels/ChainedPanels.ino new file mode 100644 index 0000000..1343d9b --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanels/ChainedPanels.ino @@ -0,0 +1,169 @@ +/****************************************************************************** + ------------------------------------------------------------------------- + Steps to create a virtual display made up of a chain of panels in a grid + ------------------------------------------------------------------------- + + Read the documentation! + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/tree/master/examples/ChainedPanels + + tl/dr: + + - Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN_TYPE. + + - Other than where the matrix is defined and matrix.begin in the setup, you + should now be using the virtual display for everything (drawing pixels, writing text etc). + You can do a find and replace of all calls if it's an existing sketch + (just make sure you don't replace the definition and the matrix.begin) + + - If the sketch makes use of MATRIX_HEIGHT or MATRIX_WIDTH, these will need to be + replaced with the width and height of your virtual screen. + Either make new defines and use that, or you can use virtualDisp.width() or .height() + +*****************************************************************************/ +// 1) Include key virtual display library + #include + +// 2) Set configuration + #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. + #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. + + #define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS + #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + #define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another + + /* Configure the serpetine chaining approach. Options are: + CHAIN_TOP_LEFT_DOWN + CHAIN_TOP_RIGHT_DOWN + CHAIN_BOTTOM_LEFT_UP + CHAIN_BOTTOM_RIGHT_UP + + The location (i.e. 'TOP_LEFT', 'BOTTOM_RIGHT') etc. refers to the starting point where + the ESP32 is located, and how the chain of panels will 'roll out' from there. + + In this example we're using 'CHAIN_BOTTOM_LEFT_UP' which would look like this in the real world: + + Chain of 4 x 64x32 panels with the ESP at the BOTTOM_LEFT: + + +---------+---------+ + | 4 | 3 | + | | | + +---------+---------+ + | 1 | 2 | + | (ESP) | | + +---------+---------+ + + */ + #define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_BOTTOM_LEFT_UP + +// 3) Create the runtime objects to use + + // placeholder for the matrix object + MatrixPanel_I2S_DMA *dma_display = nullptr; + + // placeholder for the virtual display object + VirtualMatrixPanel *virtualDisp = nullptr; + + +/****************************************************************************** + * Setup! + ******************************************************************************/ +void setup() { + + delay(2000); + Serial.begin(115200); + Serial.println(""); Serial.println(""); Serial.println(""); + Serial.println("*****************************************************"); + Serial.println(" HELLO !"); + Serial.println("*****************************************************"); + + + /****************************************************************************** + * Create physical DMA panel class AND virtual (chained) display class. + ******************************************************************************/ + + /* + The configuration for MatrixPanel_I2S_DMA object is held in HUB75_I2S_CFG structure, + All options has it's predefined default values. So we can create a new structure and redefine only the options we need + + Please refer to the '2_PatternPlasma.ino' example for detailed example of how to use the MatrixPanel_I2S_DMA configuration + */ + + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // chain length + ); + + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object + + // Sanity checks + if (NUM_ROWS <= 1) { + Serial.println(F("There is no reason to use the VirtualDisplay class for a single horizontal chain and row!")); + } + + // OK, now we can create our matrix object + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // let's adjust default brightness to about 75% + dma_display->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100% + + // Allocate memory and start DMA display + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! I2S memory allocation failed ***********"); + + // create VirtualDisplay object based on our newly created dma_display object + virtualDisp = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE); + + // So far so good, so continue + virtualDisp->fillScreen(virtualDisp->color444(0, 0, 0)); + virtualDisp->drawDisplayTest(); // draw text numbering on each screen to check connectivity + + // delay(1000); + + Serial.println("Chain of 4x 64x32 panels for this example:"); + Serial.println("+---------+---------+"); + Serial.println("| 4 | 3 |"); + Serial.println("| | |"); + Serial.println("+---------+---------+"); + Serial.println("| 1 | 2 |"); + Serial.println("| (ESP32) | |"); + Serial.println("+---------+---------+"); + + // draw blue text + virtualDisp->setFont(&FreeSansBold12pt7b); + virtualDisp->setTextColor(virtualDisp->color565(0, 0, 255)); + virtualDisp->setTextSize(3); + virtualDisp->setCursor(0, virtualDisp->height()- ((virtualDisp->height()-45)/2)); + virtualDisp->print("ABCD"); + + // Red text inside red rect (2 pix in from edge) + virtualDisp->drawRect(1,1, virtualDisp->width()-2, virtualDisp->height()-2, virtualDisp->color565(255,0,0)); + + // White line from top left to bottom right + virtualDisp->drawLine(0,0, virtualDisp->width()-1, virtualDisp->height()-1, virtualDisp->color565(255,255,255)); + + virtualDisp->drawDisplayTest(); // re draw text numbering on each screen to check connectivity + +} + +void loop() { + + +} // end loop + + +/***************************************************************************** + + Thanks to: + + * Brian Lough for the original example as raised in this issue: + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/26 + + YouTube: https://www.youtube.com/brianlough + Tindie: https://www.tindie.com/stores/brianlough/ + Twitter: https://twitter.com/witnessmenow + + * Galaxy-Man for the kind donation of panels make/test that this is possible: + https://github.com/Galaxy-Man + +*****************************************************************************/ \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanels/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanels/README.md new file mode 100644 index 0000000..c5aca88 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanels/README.md @@ -0,0 +1,51 @@ +## Chained Panels example - Chaining individual LED matrix panels to make a larger panel ## + +This is the PatternPlasma Demo adopted for use with multiple LED Matrix Panel displays arranged in a non standard order (i.e. a grid) to make a bigger display. + +![334894846_975082690567510_1362796919784291270_n](https://user-images.githubusercontent.com/89576620/224304944-94fe3483-d3cc-4aba-be0a-40b33ff901dc.jpg) + +### What do we mean by 'non standard order'? ### + +When you link / chain multiple panels together, the ESP32-HUB75-MatrixPanel-I2S-DMA library treats as one wide horizontal panel. This would be a 'standard' (default) order. + +Non-standard order is essentially the creation of a non-horizontal-only display that you can draw to in the same way you would any other display, with VirtualDisplay library looking after the pixel mapping to the physical chained panels. + +For example: You bought four (4) 64x32px panels, and wanted to use them to create a 128x64pixel display. You would use the VirtualMatrixPanel class. + +[Refer to this document](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/blob/master/doc/VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. + + +### Steps to Use ### + +1. [Refer to this document](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/blob/master/doc/VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. + +2. In your Arduino sketch, configure these defines accordingly: +``` +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. + +#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS +#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + +#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another + +#define VIRTUAL_MATRIX_CHAIN_TYPE + +``` +VIRTUAL_MATRIX_CHAIN_TYPE's: +![image](https://user-images.githubusercontent.com/12006953/224537356-e3c8e87b-0bc0-4185-8f5d-d2d3b328d176.png) + + +3. In your Arduino sketch, use the 'VirtualMatrixPanel' class instance (virtualDisp) to draw to the display (i.e. drawPixel), instead of the underling MatrixPanel_I2S_DMA class instance (dma_display). + + +#### Thanks to #### +* Brian Lough for the Virtual to Real pixel co-ordinate code. + +YouTube: https://www.youtube.com/brianlough + +Tindie: https://www.tindie.com/stores/brianlough/ + +Twitter: https://twitter.com/witnessmenow + +* Galaxy-Man for the donation of hardware for testing. diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Attractor.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Attractor.h new file mode 100644 index 0000000..668ba53 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Attractor.h @@ -0,0 +1,50 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Attractor" in "The Nature of Code" by Daniel Shiffman: http://natureofcode.com/ + * Copyright (c) 2014 Daniel Shiffman + * http://www.shiffman.net + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "Vector.h" + +class Attractor { +public: + float mass; // Mass, tied to size + float G; // Gravitational Constant + PVector location; // Location + + Attractor() { + location = PVector(MATRIX_CENTRE_X, MATRIX_CENTRE_Y); + mass = 10; + G = .5; + } + + PVector attract(Boid m) { + PVector force = location - m.location; // Calculate direction of force + float d = force.mag(); // Distance between objects + d = constrain(d, 5.0, 32.0); // Limiting the distance to eliminate "extreme" results for very close or very far objects + force.normalize(); // Normalize vector (distance doesn't matter here, we just want this vector for direction) + float strength = (G * mass * m.mass) / (d * d); // Calculate gravitational force magnitude + force *= strength; // Get force vector --> magnitude * direction + return force; + } +}; diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Boid.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Boid.h new file mode 100644 index 0000000..fa5a9e6 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Boid.h @@ -0,0 +1,326 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Flocking" in "The Nature of Code" by Daniel Shiffman: http://natureofcode.com/ + * Copyright (c) 2014 Daniel Shiffman + * http://www.shiffman.net + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Flocking +// Daniel Shiffman +// The Nature of Code, Spring 2009 + +// Boid class +// Methods for Separation, Cohesion, Alignment added + +class Boid { + public: + + PVector location; + PVector velocity; + PVector acceleration; + float maxforce; // Maximum steering force + float maxspeed; // Maximum speed + + float desiredseparation = 4; + float neighbordist = 8; + byte colorIndex = 0; + float mass; + + boolean enabled = true; + + Boid() {} + + Boid(float x, float y) { + acceleration = PVector(0, 0); + velocity = PVector(randomf(), randomf()); + location = PVector(x, y); + maxspeed = 1.5; + maxforce = 0.05; + } + + static float randomf() { + return mapfloat(random(0, 255), 0, 255, -.5, .5); + } + + static float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } + + void run(Boid boids [], uint8_t boidCount) { + flock(boids, boidCount); + update(); + // wrapAroundBorders(); + // render(); + } + + // Method to update location + void update() { + // Update velocity + velocity += acceleration; + // Limit speed + velocity.limit(maxspeed); + location += velocity; + // Reset acceleration to 0 each cycle + acceleration *= 0; + } + + void applyForce(PVector force) { + // We could add mass here if we want A = F / M + acceleration += force; + } + + void repelForce(PVector obstacle, float radius) { + //Force that drives boid away from obstacle. + + PVector futPos = location + velocity; //Calculate future position for more effective behavior. + PVector dist = obstacle - futPos; + float d = dist.mag(); + + if (d <= radius) { + PVector repelVec = location - obstacle; + repelVec.normalize(); + if (d != 0) { //Don't divide by zero. + // float scale = 1.0 / d; //The closer to the obstacle, the stronger the force. + repelVec.normalize(); + repelVec *= (maxforce * 7); + if (repelVec.mag() < 0) { //Don't let the boids turn around to avoid the obstacle. + repelVec.y = 0; + } + } + applyForce(repelVec); + } + } + + // We accumulate a new acceleration each time based on three rules + void flock(Boid boids [], uint8_t boidCount) { + PVector sep = separate(boids, boidCount); // Separation + PVector ali = align(boids, boidCount); // Alignment + PVector coh = cohesion(boids, boidCount); // Cohesion + // Arbitrarily weight these forces + sep *= 1.5; + ali *= 1.0; + coh *= 1.0; + // Add the force vectors to acceleration + applyForce(sep); + applyForce(ali); + applyForce(coh); + } + + // Separation + // Method checks for nearby boids and steers away + PVector separate(Boid boids [], uint8_t boidCount) { + PVector steer = PVector(0, 0); + int count = 0; + // For every boid in the system, check if it's too close + for (int i = 0; i < boidCount; i++) { + Boid other = boids[i]; + if (!other.enabled) + continue; + float d = location.dist(other.location); + // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) + if ((d > 0) && (d < desiredseparation)) { + // Calculate vector pointing away from neighbor + PVector diff = location - other.location; + diff.normalize(); + diff /= d; // Weight by distance + steer += diff; + count++; // Keep track of how many + } + } + // Average -- divide by how many + if (count > 0) { + steer /= (float) count; + } + + // As long as the vector is greater than 0 + if (steer.mag() > 0) { + // Implement Reynolds: Steering = Desired - Velocity + steer.normalize(); + steer *= maxspeed; + steer -= velocity; + steer.limit(maxforce); + } + return steer; + } + + // Alignment + // For every nearby boid in the system, calculate the average velocity + PVector align(Boid boids [], uint8_t boidCount) { + PVector sum = PVector(0, 0); + int count = 0; + for (int i = 0; i < boidCount; i++) { + Boid other = boids[i]; + if (!other.enabled) + continue; + float d = location.dist(other.location); + if ((d > 0) && (d < neighbordist)) { + sum += other.velocity; + count++; + } + } + if (count > 0) { + sum /= (float) count; + sum.normalize(); + sum *= maxspeed; + PVector steer = sum - velocity; + steer.limit(maxforce); + return steer; + } + else { + return PVector(0, 0); + } + } + + // Cohesion + // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location + PVector cohesion(Boid boids [], uint8_t boidCount) { + PVector sum = PVector(0, 0); // Start with empty vector to accumulate all locations + int count = 0; + for (int i = 0; i < boidCount; i++) { + Boid other = boids[i]; + if (!other.enabled) + continue; + float d = location.dist(other.location); + if ((d > 0) && (d < neighbordist)) { + sum += other.location; // Add location + count++; + } + } + if (count > 0) { + sum /= count; + return seek(sum); // Steer towards the location + } + else { + return PVector(0, 0); + } + } + + // A method that calculates and applies a steering force towards a target + // STEER = DESIRED MINUS VELOCITY + PVector seek(PVector target) { + PVector desired = target - location; // A vector pointing from the location to the target + // Normalize desired and scale to maximum speed + desired.normalize(); + desired *= maxspeed; + // Steering = Desired minus Velocity + PVector steer = desired - velocity; + steer.limit(maxforce); // Limit to maximum steering force + return steer; + } + + // A method that calculates a steering force towards a target + // STEER = DESIRED MINUS VELOCITY + void arrive(PVector target) { + PVector desired = target - location; // A vector pointing from the location to the target + float d = desired.mag(); + // Normalize desired and scale with arbitrary damping within 100 pixels + desired.normalize(); + if (d < 4) { + float m = map(d, 0, 100, 0, maxspeed); + desired *= m; + } + else { + desired *= maxspeed; + } + + // Steering = Desired minus Velocity + PVector steer = desired - velocity; + steer.limit(maxforce); // Limit to maximum steering force + applyForce(steer); + //Serial.println(d); + } + + void wrapAroundBorders() { + if (location.x < 0) location.x = VPANEL_W - 1; + if (location.y < 0) location.y = VPANEL_H - 1; + if (location.x >= VPANEL_W) location.x = 0; + if (location.y >= VPANEL_H) location.y = 0; + } + + void avoidBorders() { + PVector desired = velocity; + + if (location.x < 8) desired = PVector(maxspeed, velocity.y); + if (location.x >= VPANEL_W - 8) desired = PVector(-maxspeed, velocity.y); + if (location.y < 8) desired = PVector(velocity.x, maxspeed); + if (location.y >= VPANEL_H - 8) desired = PVector(velocity.x, -maxspeed); + + if (desired != velocity) { + PVector steer = desired - velocity; + steer.limit(maxforce); + applyForce(steer); + } + + if (location.x < 0) location.x = 0; + if (location.y < 0) location.y = 0; + if (location.x >= VPANEL_W) location.x = VPANEL_W - 1; + if (location.y >= VPANEL_H) location.y = VPANEL_H - 1; + } + + bool bounceOffBorders(float bounce) { + bool bounced = false; + + if (location.x >= VPANEL_W) { + location.x = VPANEL_W - 1; + velocity.x *= -bounce; + bounced = true; + } + else if (location.x < 0) { + location.x = 0; + velocity.x *= -bounce; + bounced = true; + } + + if (location.y >= VPANEL_H) { + location.y = VPANEL_H - 1; + velocity.y *= -bounce; + bounced = true; + } + else if (location.y < 0) { + location.y = 0; + velocity.y *= -bounce; + bounced = true; + } + + return bounced; + } + + void render() { + //// Draw a triangle rotated in the direction of velocity + //float theta = velocity.heading2D() + radians(90); + //fill(175); + //stroke(0); + //pushMatrix(); + //translate(location.x,location.y); + //rotate(theta); + //beginShape(TRIANGLES); + //vertex(0, -r*2); + //vertex(-r, r*2); + //vertex(r, r*2); + //endShape(); + //popMatrix(); + //matrix.drawBackgroundPixelRGB888(location.x, location.y, CRGB::Blue); + } +}; + +static const uint8_t AVAILABLE_BOID_COUNT = 40; +Boid boids[AVAILABLE_BOID_COUNT]; diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino new file mode 100644 index 0000000..f1cd7de --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino @@ -0,0 +1,209 @@ +#include + +/* Default library pin configuration for the reference + you can redefine only ones you need later on object creation + +#define R1 25 +#define G1 26 +#define BL1 27 +#define R2 14 +#define G2 12 +#define BL2 13 +#define CH_A 23 +#define CH_B 19 +#define CH_C 5 +#define CH_D 17 +#define CH_E -1 // assign to any available pin if using two panels or 64x64 panels with 1/32 scan +#define CLK 16 +#define LAT 4 +#define OE 15 + +*/ + + +/* -------------------------- Display Config Initialisation -------------------- */ +// Assume we have four 64x32 panels daisy-chained and ESP32 attached to the bottom right corner +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. + +#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS +#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW +#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another + +// Change this to your needs, for details on VirtualPanel pls see ChainedPanels example +#define SERPENT false +#define TOPDOWN false + +// Virtual Panel dimensions - our combined panel would be a square 4x4 modules with a combined resolution of 128x128 pixels +#define VPANEL_W PANEL_RES_X*NUM_COLS // Kosso: All Pattern files have had the MATRIX_WIDTH and MATRIX_HEIGHT replaced by these. +#define VPANEL_H PANEL_RES_Y*NUM_ROWS // + +// Kosso added: Button with debounce +#define BTN_PIN 0 // Pattern advance. Using EPS32 Boot button. +int buttonState; // the current reading from the input pin +int lastButtonState = LOW; // the previous reading from the input pin +unsigned long lastDebounceTime = 0; // the last time the output pin was toggled +unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers + +// The palettes are set to change every 60 seconds. + +// Kosso added: Non-volatile memory to save last pattern index. +#include +Preferences preferences; +int lastPattern = 0; + + +/* -------------------------- Class Initialisation -------------------------- */ +#include +#include // Used for some mathematics calculations and effects. + +// placeholder for the matrix object +MatrixPanel_I2S_DMA *matrix = nullptr; + +// placeholder for the virtual display object +VirtualMatrixPanel *virtualDisp = nullptr; + +// Aurora related +#include "Effects.h" +Effects effects; + +#include "Drawable.h" +#include "Playlist.h" +//#include "Geometry.h" +#include "Patterns.h" +Patterns patterns; + +/* -------------------------- Some variables -------------------------- */ +unsigned long ms_current = 0; +unsigned long ms_previous = 0; +unsigned long ms_animation_max_duration = 20000; // 10 seconds +unsigned long next_frame = 0; + +void listPatterns(); + +void setup() +{ + // Setup serial interface + Serial.begin(115200); + delay(250); + + // Added a button to manually advance the pattern index. + pinMode(BTN_PIN, INPUT); + // For saving last pattern index. TO reboot with same. + preferences.begin("RGBMATRIX", false); + lastPattern = preferences.getInt("lastPattern", 0); + + // Configure your matrix setup here + HUB75_I2S_CFG mxconfig(PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN); + + // custom pin mapping (if required) + //HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; + //mxconfig.gpio = _pins; + + // in case that we use panels based on FM6126A chip, we can change that + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // FM6126A panels could be cloked at 20MHz with no visual artefacts + // mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_20M; + + // OK, now we can create our matrix object + matrix = new MatrixPanel_I2S_DMA(mxconfig); + + // Allocate memory and start DMA display + if( not matrix->begin() ) + Serial.println("****** !KABOOM! I2S memory allocation failed ***********"); + + // let's adjust default brightness to about 75% + matrix->setBrightness8(96); // range is 0-255, 0 - 0%, 255 - 100% + + // create VirtualDisplay object based on our newly created dma_display object + virtualDisp = new VirtualMatrixPanel((*matrix), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, CHAIN_TOP_LEFT_DOWN); + + Serial.println("**************** Starting Aurora Effects Demo ****************"); + + Serial.print("MATRIX_WIDTH: "); Serial.println(PANEL_RES_X*PANEL_CHAIN); + Serial.print("MATRIX_HEIGHT: "); Serial.println(PANEL_RES_Y); + +#ifdef VPANEL_W + Serial.println("VIRTUAL PANEL WIDTH " + String(VPANEL_W)); + Serial.println("VIRTUAL PANEL HEIGHT " + String(VPANEL_H)); +#endif + + // setup the effects generator + effects.Setup(); + + delay(500); + Serial.println("Effects being loaded: "); + listPatterns(); + + Serial.println("LastPattern index: " + String(lastPattern)); + + patterns.setPattern(lastPattern); // // simple noise + patterns.start(); + + Serial.print("Starting with pattern: "); + Serial.println(patterns.getCurrentPatternName()); + + preferences.end(); + +} + + +void patternAdvance(){ + // Go to next pattern in the list (se Patterns.h) + patterns.stop(); + patterns.moveRandom(1); + //patterns.move(1); + patterns.start(); + // Select a random palette as well + effects.RandomPalette(); + Serial.print("Changing pattern to: "); + Serial.println(patterns.getCurrentPatternName()); + //Serial.println(patterns.getPatternIndex()); + //lastPattern = patterns.getPatternIndex(); + // Save last index. + preferences.begin("RGBMATRIX", false); + preferences.putInt("lastPattern", lastPattern); + preferences.end(); + +} + +void loop() +{ + // Boot button Pattern advance with debounce + int reading = digitalRead(BTN_PIN); + if (reading != lastButtonState) { + lastDebounceTime = millis(); + } + + if ((millis() - lastDebounceTime) > debounceDelay) { + if (reading != buttonState) { + buttonState = reading; + if (buttonState == LOW) { + Serial.println("NEXT PATTERN ..."); + patternAdvance(); + } + } + } + lastButtonState = reading; + // end button debounce + + ms_current = millis(); + + if ( (ms_current - ms_previous) > ms_animation_max_duration ) + { + patternAdvance(); + // just auto-change the palette + effects.RandomPalette(); + ms_previous = ms_current; + } + + if ( next_frame < ms_current) + next_frame = patterns.drawFrame() + ms_current; + +} + + +void listPatterns() { + patterns.listPatterns(); +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Drawable.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Drawable.h new file mode 100644 index 0000000..b2169fe --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Drawable.h @@ -0,0 +1,55 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Drawable_H +#define Drawable_H + +class Drawable{ +public: + char* name; + + virtual bool isRunnable() { + return false; + } + + virtual bool isPlaylist() { + return false; + } + + // a single frame should be drawn as fast as possible, without any delay or blocking + // return how many millisecond delay is requested before the next call to drawFrame() + virtual unsigned int drawFrame() { + matrix->fillScreen(0); + //backgroundLayer.fillScreen({ 0, 0, 0 }); + return 0; + }; + + virtual void printTesting() + { + Serial.println("Testing..."); + } + + virtual void start() {}; + virtual void stop() {}; +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Effects.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Effects.h new file mode 100644 index 0000000..3d4affb --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Effects.h @@ -0,0 +1,852 @@ + +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Funky Clouds" by Stefan Petrick: https://gist.github.com/anonymous/876f908333cd95315c35 + * Portions of this code are adapted from "NoiseSmearing" by Stefan Petrick: https://gist.github.com/StefanPetrick/9ee2f677dbff64e3ba7a + * Copyright (c) 2014 Stefan Petrick + * http://www.stefan-petrick.de/wordpress_beta + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Effects_H +#define Effects_H + +/* ---------------------------- GLOBAL CONSTANTS ----------------------------- */ + +const int MATRIX_CENTER_X = VPANEL_W / 2; +const int MATRIX_CENTER_Y = VPANEL_H / 2; +// US vs GB, huh? :) +//const byte MATRIX_CENTRE_X = MATRIX_CENTER_X - 1; +//const byte MATRIX_CENTRE_Y = MATRIX_CENTER_Y - 1; +#define MATRIX_CENTRE_X MATRIX_CENTER_X +#define MATRIX_CENTRE_Y MATRIX_CENTER_Y + + +const uint16_t NUM_LEDS = (VPANEL_W * VPANEL_H) + 1; // one led spare to capture out of bounds + +// forward declaration +uint16_t XY16( uint16_t x, uint16_t y); + +/* Convert x,y co-ordinate to flat array index. + * x and y positions start from 0, so must not be >= 'real' panel width or height + * (i.e. 64 pixels or 32 pixels.). Max value: VPANEL_W-1 etc. + * Ugh... uint8_t - really??? this weak method can't cope with 256+ pixel matrices :( + */ +uint16_t XY( uint8_t x, uint8_t y) +{ + return XY16(x, y); +} + +/** + * The one for 256+ matrices + * otherwise this: + * for (uint8_t i = 0; i < VPANEL_W; i++) {} + * turns into an infinite loop + */ +uint16_t XY16( uint16_t x, uint16_t y) +{ + if( x >= VPANEL_W) return 0; + if( y >= VPANEL_H) return 0; + + return (y * VPANEL_W) + x + 1; // everything offset by one to compute out of bounds stuff - never displayed by ShowFrame() +} + + +uint8_t beatcos8(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0) +{ + uint8_t beat = beat8(beats_per_minute, timebase); + uint8_t beatcos = cos8(beat + phase_offset); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatcos, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} + +uint8_t mapsin8(uint8_t theta, uint8_t lowest = 0, uint8_t highest = 255) { + uint8_t beatsin = sin8(theta); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatsin, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} + +uint8_t mapcos8(uint8_t theta, uint8_t lowest = 0, uint8_t highest = 255) { + uint8_t beatcos = cos8(theta); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatcos, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} + +// Array of temperature readings at each simulation cell +//byte heat[NUM_LEDS]; // none of the currently enabled effects uses this + +uint32_t noise_x; +uint32_t noise_y; +uint32_t noise_z; +uint32_t noise_scale_x; +uint32_t noise_scale_y; + +//uint8_t noise[VPANEL_W][VPANEL_H]; +uint8_t **noise = nullptr; // we will allocate mem later +uint8_t noisesmoothing; + +class Effects { +public: + CRGB *leds; + + Effects(){ + // we do dynamic allocation for leds buffer, otherwise esp32 toolchain can't link static arrays of such a big size for 256+ matrices + leds = (CRGB *)malloc(NUM_LEDS * sizeof(CRGB)); + + // allocate mem for noise effect + // (there should be some guards for malloc errors eventually) + noise = (uint8_t **)malloc(VPANEL_W * sizeof(uint8_t *)); + for (int i = 0; i < VPANEL_W; ++i) { + noise[i] = (uint8_t *)malloc(VPANEL_H * sizeof(uint8_t)); + } + + ClearFrame(); + } + ~Effects(){ + free(leds); + for (int i = 0; i < VPANEL_W; ++i) { + free(noise[i]); + } + free(noise); + } + + /* The only 'framebuffer' we have is what is contained in the leds and leds2 variables. + * We don't store what the color a particular pixel might be, other than when it's turned + * into raw electrical signal output gobbly-gook (i.e. the DMA matrix buffer), but this * is not reversible. + * + * As such, any time these effects want to write a pixel color, we first have to update + * the leds or leds2 array, and THEN write it to the RGB panel. This enables us to 'look up' the array to see what a pixel color was previously, each drawFrame(). + */ + void drawBackgroundFastLEDPixelCRGB(int16_t x, int16_t y, CRGB color) + { + leds[XY(x, y)] = color; + //matrix.drawPixelRGB888(x, y, color.r, color.g, color.b); + } + + // write one pixel with the specified color from the current palette to coordinates + void Pixel(int x, int y, uint8_t colorIndex) { + leds[XY(x, y)] = ColorFromCurrentPalette(colorIndex); + //matrix.drawPixelRGB888(x, y, temp.r, temp.g, temp.b); // now draw it? + } + + void PrepareFrame() { + // leds = (CRGB*) backgroundLayer.backBuffer(); + } + + void ShowFrame() { + //#if (FASTLED_VERSION >= 3001000) + // nblendPaletteTowardPalette(currentPalette, targetPalette, 24); + //#else + currentPalette = targetPalette; + //#endif + + // backgroundLayer.swapBuffers(); + // leds = (CRGB*) backgroundLayer.backBuffer(); + // LEDS.countFPS(); + + for (int y=0; ydrawPixelRGB888( x, y, leds[_pixel].r, leds[_pixel].g, leds[_pixel].b); + } // end loop to copy fast led to the dma matrix + } + } + + // scale the brightness of the screenbuffer down + void DimAll(byte value) + { + for (int i = 0; i < NUM_LEDS; i++) + { + leds[i].nscale8(value); + } + } + + void ClearFrame() + { + memset(leds, 0x00, NUM_LEDS * sizeof(CRGB)); // flush + } + + + +/* + void CircleStream(uint8_t value) { + DimAll(value); ShowFrame(); + + for (uint8_t offset = 0; offset < MATRIX_CENTER_X; offset++) { + boolean hasprev = false; + uint16_t prevxy = 0; + + for (uint8_t theta = 0; theta < 255; theta++) { + uint8_t x = mapcos8(theta, offset, (VPANEL_W - 1) - offset); + uint8_t y = mapsin8(theta, offset, (VPANEL_H - 1) - offset); + + uint16_t xy = XY(x, y); + + if (hasprev) { + leds[prevxy] += leds[xy]; + } + + prevxy = xy; + hasprev = true; + } + } + + for (uint8_t x = 0; x < VPANEL_W; x++) { + for (uint8_t y = 0; y < VPANEL_H; y++) { + uint16_t xy = XY(x, y); + leds[xy] = leds2[xy]; + leds[xy].nscale8(value); + leds2[xy].nscale8(value); + } + } + } +*/ + + // palettes + static const int paletteCount = 10; + int paletteIndex = -1; + TBlendType currentBlendType = LINEARBLEND; + CRGBPalette16 currentPalette; + CRGBPalette16 targetPalette; + char* currentPaletteName; + + static const int HeatColorsPaletteIndex = 6; + static const int RandomPaletteIndex = 9; + + void Setup() { + currentPalette = RainbowColors_p; + loadPalette(0); + NoiseVariablesSetup(); + } + + void CyclePalette(int offset = 1) { + loadPalette(paletteIndex + offset); + } + + void RandomPalette() { + loadPalette(RandomPaletteIndex); + } + + void loadPalette(int index) { + paletteIndex = index; + + if (paletteIndex >= paletteCount) + paletteIndex = 0; + else if (paletteIndex < 0) + paletteIndex = paletteCount - 1; + + switch (paletteIndex) { + case 0: + targetPalette = RainbowColors_p; + currentPaletteName = (char *)"Rainbow"; + break; + //case 1: + // targetPalette = RainbowStripeColors_p; + // currentPaletteName = (char *)"RainbowStripe"; + // break; + case 1: + targetPalette = OceanColors_p; + currentPaletteName = (char *)"Ocean"; + break; + case 2: + targetPalette = CloudColors_p; + currentPaletteName = (char *)"Cloud"; + break; + case 3: + targetPalette = ForestColors_p; + currentPaletteName = (char *)"Forest"; + break; + case 4: + targetPalette = PartyColors_p; + currentPaletteName = (char *)"Party"; + break; + case 5: + setupGrayscalePalette(); + currentPaletteName = (char *)"Grey"; + break; + case HeatColorsPaletteIndex: + targetPalette = HeatColors_p; + currentPaletteName = (char *)"Heat"; + break; + case 7: + targetPalette = LavaColors_p; + currentPaletteName = (char *)"Lava"; + break; + case 8: + setupIcePalette(); + currentPaletteName = (char *)"Ice"; + break; + case RandomPaletteIndex: + loadPalette(random(0, paletteCount - 1)); + paletteIndex = RandomPaletteIndex; + currentPaletteName = (char *)"Random"; + break; + } + } + + void setPalette(String paletteName) { + if (paletteName == "Rainbow") + loadPalette(0); + //else if (paletteName == "RainbowStripe") + // loadPalette(1); + else if (paletteName == "Ocean") + loadPalette(1); + else if (paletteName == "Cloud") + loadPalette(2); + else if (paletteName == "Forest") + loadPalette(3); + else if (paletteName == "Party") + loadPalette(4); + else if (paletteName == "Grayscale") + loadPalette(5); + else if (paletteName == "Heat") + loadPalette(6); + else if (paletteName == "Lava") + loadPalette(7); + else if (paletteName == "Ice") + loadPalette(8); + else if (paletteName == "Random") + RandomPalette(); + } + + void listPalettes() { + Serial.println(F("{")); + Serial.print(F(" \"count\": ")); + Serial.print(paletteCount); + Serial.println(","); + Serial.println(F(" \"results\": [")); + + String paletteNames [] = { + "Rainbow", + // "RainbowStripe", + "Ocean", + "Cloud", + "Forest", + "Party", + "Grayscale", + "Heat", + "Lava", + "Ice", + "Random" + }; + + for (int i = 0; i < paletteCount; i++) { + Serial.print(F(" \"")); + Serial.print(paletteNames[i]); + if (i == paletteCount - 1) + Serial.println(F("\"")); + else + Serial.println(F("\",")); + } + + Serial.println(" ]"); + Serial.println("}"); + } + + void setupGrayscalePalette() { + targetPalette = CRGBPalette16(CRGB::Black, CRGB::White); + } + + void setupIcePalette() { + targetPalette = CRGBPalette16(CRGB::Black, CRGB::Blue, CRGB::Aqua, CRGB::White); + } + + // Oscillators and Emitters + + // the oscillators: linear ramps 0-255 + byte osci[6]; + + // sin8(osci) swinging between 0 to VPANEL_W - 1 + byte p[6]; + + // set the speeds (and by that ratios) of the oscillators here + void MoveOscillators() { + osci[0] = osci[0] + 5; + osci[1] = osci[1] + 2; + osci[2] = osci[2] + 3; + osci[3] = osci[3] + 4; + osci[4] = osci[4] + 1; + if (osci[4] % 2 == 0) + osci[5] = osci[5] + 1; // .5 + for (int i = 0; i < 4; i++) { + p[i] = map8(sin8(osci[i]), 0, VPANEL_W - 1); //why? to keep the result in the range of 0-VPANEL_W (matrix size) + } + } + + + // All the caleidoscope functions work directly within the screenbuffer (leds array). + // Draw whatever you like in the area x(0-15) and y (0-15) and then copy it arround. + + // rotates the first 16x16 quadrant 3 times onto a 32x32 (+90 degrees rotation for each one) + void Caleidoscope1() { + for (int x = 0; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < MATRIX_CENTER_Y; y++) { + leds[XY16(VPANEL_W - 1 - x, y)] = leds[XY16(x, y)]; + leds[XY16(VPANEL_W - 1 - x, VPANEL_H - 1 - y)] = leds[XY16(x, y)]; + leds[XY16(x, VPANEL_H - 1 - y)] = leds[XY16(x, y)]; + } + } + } + + + // mirror the first 16x16 quadrant 3 times onto a 32x32 + void Caleidoscope2() { + for (int x = 0; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < MATRIX_CENTER_Y; y++) { + leds[XY16(VPANEL_W - 1 - x, y)] = leds[XY16(y, x)]; + leds[XY16(x, VPANEL_H - 1 - y)] = leds[XY16(y, x)]; + leds[XY16(VPANEL_W - 1 - x, VPANEL_H - 1 - y)] = leds[XY16(x, y)]; + } + } + } + + // copy one diagonal triangle into the other one within a 16x16 + void Caleidoscope3() { + for (int x = 0; x <= MATRIX_CENTRE_X && x < VPANEL_H; x++) { + for (int y = 0; y <= x && y= 0; y--) { + leds[XY16(x, y)] = leds[XY16(y, x)]; + } + } + } + + void Caleidoscope6() { + for (int x = 1; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 7)] = leds[XY16(x, 0)]; + } //a + for (int x = 2; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 6)] = leds[XY16(x, 1)]; + } //b + for (int x = 3; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 5)] = leds[XY16(x, 2)]; + } //c + for (int x = 4; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 4)] = leds[XY16(x, 3)]; + } //d + for (int x = 5; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 3)] = leds[XY16(x, 4)]; + } //e + for (int x = 6; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 2)] = leds[XY16(x, 5)]; + } //f + for (int x = 7; x < MATRIX_CENTER_X; x++) { + leds[XY16(7 - x, 1)] = leds[XY16(x, 6)]; + } //g + } + + // create a square twister to the left or counter-clockwise + // x and y for center, r for radius + void SpiralStream(int x, int y, int r, byte dimm) { + for (int d = r; d >= 0; d--) { // from the outside to the inside + for (int i = x - d; i <= x + d; i++) { + leds[XY16(i, y - d)] += leds[XY16(i + 1, y - d)]; // lowest row to the right + leds[XY16(i, y - d)].nscale8(dimm); + } + for (int i = y - d; i <= y + d; i++) { + leds[XY16(x + d, i)] += leds[XY16(x + d, i + 1)]; // right column up + leds[XY16(x + d, i)].nscale8(dimm); + } + for (int i = x + d; i >= x - d; i--) { + leds[XY16(i, y + d)] += leds[XY16(i - 1, y + d)]; // upper row to the left + leds[XY16(i, y + d)].nscale8(dimm); + } + for (int i = y + d; i >= y - d; i--) { + leds[XY16(x - d, i)] += leds[XY16(x - d, i - 1)]; // left column down + leds[XY16(x - d, i)].nscale8(dimm); + } + } + } + + // expand everything within a circle + void Expand(int centerX, int centerY, int radius, byte dimm) { + if (radius == 0) + return; + + int currentRadius = radius; + + while (currentRadius > 0) { + int a = radius, b = 0; + int radiusError = 1 - a; + + int nextRadius = currentRadius - 1; + int nextA = nextRadius - 1, nextB = 0; + int nextRadiusError = 1 - nextA; + + while (a >= b) + { + // move them out one pixel on the radius + leds[XY16(a + centerX, b + centerY)] = leds[XY16(nextA + centerX, nextB + centerY)]; + leds[XY16(b + centerX, a + centerY)] = leds[XY16(nextB + centerX, nextA + centerY)]; + leds[XY16(-a + centerX, b + centerY)] = leds[XY16(-nextA + centerX, nextB + centerY)]; + leds[XY16(-b + centerX, a + centerY)] = leds[XY16(-nextB + centerX, nextA + centerY)]; + leds[XY16(-a + centerX, -b + centerY)] = leds[XY16(-nextA + centerX, -nextB + centerY)]; + leds[XY16(-b + centerX, -a + centerY)] = leds[XY16(-nextB + centerX, -nextA + centerY)]; + leds[XY16(a + centerX, -b + centerY)] = leds[XY16(nextA + centerX, -nextB + centerY)]; + leds[XY16(b + centerX, -a + centerY)] = leds[XY16(nextB + centerX, -nextA + centerY)]; + + // dim them + leds[XY16(a + centerX, b + centerY)].nscale8(dimm); + leds[XY16(b + centerX, a + centerY)].nscale8(dimm); + leds[XY16(-a + centerX, b + centerY)].nscale8(dimm); + leds[XY16(-b + centerX, a + centerY)].nscale8(dimm); + leds[XY16(-a + centerX, -b + centerY)].nscale8(dimm); + leds[XY16(-b + centerX, -a + centerY)].nscale8(dimm); + leds[XY16(a + centerX, -b + centerY)].nscale8(dimm); + leds[XY16(b + centerX, -a + centerY)].nscale8(dimm); + + b++; + if (radiusError < 0) + radiusError += 2 * b + 1; + else + { + a--; + radiusError += 2 * (b - a + 1); + } + + nextB++; + if (nextRadiusError < 0) + nextRadiusError += 2 * nextB + 1; + else + { + nextA--; + nextRadiusError += 2 * (nextB - nextA + 1); + } + } + + currentRadius--; + } + } + + // give it a linear tail to the right + void StreamRight(byte scale, int fromX = 0, int toX = VPANEL_W, int fromY = 0, int toY = VPANEL_H) + { + for (int x = fromX + 1; x < toX; x++) { + for (int y = fromY; y < toY; y++) { + leds[XY16(x, y)] += leds[XY16(x - 1, y)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int y = fromY; y < toY; y++) + leds[XY16(0, y)].nscale8(scale); + } + + // give it a linear tail to the left + void StreamLeft(byte scale, int fromX = VPANEL_W, int toX = 0, int fromY = 0, int toY = VPANEL_H) + { + for (int x = toX; x < fromX; x++) { + for (int y = fromY; y < toY; y++) { + leds[XY16(x, y)] += leds[XY16(x + 1, y)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int y = fromY; y < toY; y++) + leds[XY16(0, y)].nscale8(scale); + } + + // give it a linear tail downwards + void StreamDown(byte scale) + { + for (int x = 0; x < VPANEL_W; x++) { + for (int y = 1; y < VPANEL_H; y++) { + leds[XY16(x, y)] += leds[XY16(x, y - 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int x = 0; x < VPANEL_W; x++) + leds[XY16(x, 0)].nscale8(scale); + } + + // give it a linear tail upwards + void StreamUp(byte scale) + { + for (int x = 0; x < VPANEL_W; x++) { + for (int y = VPANEL_H - 2; y >= 0; y--) { + leds[XY16(x, y)] += leds[XY16(x, y + 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int x = 0; x < VPANEL_W; x++) + leds[XY16(x, VPANEL_H - 1)].nscale8(scale); + } + + // give it a linear tail up and to the left + void StreamUpAndLeft(byte scale) + { + for (int x = 0; x < VPANEL_W - 1; x++) { + for (int y = VPANEL_H - 2; y >= 0; y--) { + leds[XY16(x, y)] += leds[XY16(x + 1, y + 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + for (int x = 0; x < VPANEL_W; x++) + leds[XY16(x, VPANEL_H - 1)].nscale8(scale); + for (int y = 0; y < VPANEL_H; y++) + leds[XY16(VPANEL_W - 1, y)].nscale8(scale); + } + + // give it a linear tail up and to the right + void StreamUpAndRight(byte scale) + { + for (int x = 0; x < VPANEL_W - 1; x++) { + for (int y = VPANEL_H - 2; y >= 0; y--) { + leds[XY16(x + 1, y)] += leds[XY16(x, y + 1)]; + leds[XY16(x, y)].nscale8(scale); + } + } + // fade the bottom row + for (int x = 0; x < VPANEL_W; x++) + leds[XY16(x, VPANEL_H - 1)].nscale8(scale); + + // fade the right column + for (int y = 0; y < VPANEL_H; y++) + leds[XY16(VPANEL_W - 1, y)].nscale8(scale); + } + + // just move everything one line down + void MoveDown() { + for (int y = VPANEL_H - 1; y > 0; y--) { + for (int x = 0; x < VPANEL_W; x++) { + leds[XY16(x, y)] = leds[XY16(x, y - 1)]; + } + } + } + + // just move everything one line down + void VerticalMoveFrom(int start, int end) { + for (int y = end; y > start; y--) { + for (int x = 0; x < VPANEL_W; x++) { + leds[XY16(x, y)] = leds[XY16(x, y - 1)]; + } + } + } + + // copy the rectangle defined with 2 points x0, y0, x1, y1 + // to the rectangle beginning at x2, x3 + void Copy(byte x0, byte y0, byte x1, byte y1, byte x2, byte y2) { + for (int y = y0; y < y1 + 1; y++) { + for (int x = x0; x < x1 + 1; x++) { + leds[XY16(x + x2 - x0, y + y2 - y0)] = leds[XY16(x, y)]; + } + } + } + + // rotate + copy triangle (MATRIX_CENTER_X*MATRIX_CENTER_X) + void RotateTriangle() { + for (int x = 1; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < x; y++) { + leds[XY16(x, 7 - y)] = leds[XY16(7 - x, y)]; + } + } + } + + // mirror + copy triangle (MATRIX_CENTER_X*MATRIX_CENTER_X) + void MirrorTriangle() { + for (int x = 1; x < MATRIX_CENTER_X; x++) { + for (int y = 0; y < x; y++) { + leds[XY16(7 - y, x)] = leds[XY16(7 - x, y)]; + } + } + } + + // draw static rainbow triangle pattern (MATRIX_CENTER_XxWIDTH / 2) + // (just for debugging) + void RainbowTriangle() { + for (int i = 0; i < MATRIX_CENTER_X; i++) { + for (int j = 0; j <= i; j++) { + Pixel(7 - i, j, i * j * 4); + } + } + } + + void BresenhamLine(int x0, int y0, int x1, int y1, byte colorIndex) + { + BresenhamLine(x0, y0, x1, y1, ColorFromCurrentPalette(colorIndex)); + } + + void BresenhamLine(int x0, int y0, int x1, int y1, CRGB color) + { + int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; + for (;;) { + leds[XY16(x0, y0)] += color; + if (x0 == x1 && y0 == y1) break; + e2 = 2 * err; + if (e2 > dy) { + err += dy; + x0 += sx; + } + if (e2 < dx) { + err += dx; + y0 += sy; + } + } + } + + // write one pixel with the specified color from the current palette to coordinates + /* + void Pixel(int x, int y, uint8_t colorIndex) { + leds[XY(x, y)] = ColorFromCurrentPalette(colorIndex); + matrix.drawBackgroundPixelRGB888(x,y, leds[XY(x, y)]); // now draw it? + } + */ + + CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) { + return ColorFromPalette(currentPalette, index, brightness, currentBlendType); + } + + CRGB HsvToRgb(uint8_t h, uint8_t s, uint8_t v) { + CHSV hsv = CHSV(h, s, v); + CRGB rgb; + hsv2rgb_spectrum(hsv, rgb); + return rgb; + } + + void NoiseVariablesSetup() { + noisesmoothing = 200; + + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + noise_scale_x = 6000; + noise_scale_y = 6000; + } + + void FillNoise() { + for (uint16_t i = 0; i < VPANEL_W; i++) { + uint32_t ioffset = noise_scale_x * (i - MATRIX_CENTRE_Y); + + for (uint16_t j = 0; j < VPANEL_H; j++) { + uint32_t joffset = noise_scale_y * (j - MATRIX_CENTRE_Y); + + byte data = inoise16(noise_x + ioffset, noise_y + joffset, noise_z) >> 8; + + uint8_t olddata = noise[i][j]; + uint8_t newdata = scale8(olddata, noisesmoothing) + scale8(data, 256 - noisesmoothing); + data = newdata; + + noise[i][j] = data; + } + } + } + + // non leds2 memory version. + void MoveX(byte delta) + { + + CRGB tmp = 0; + + for (int y = 0; y < VPANEL_H; y++) + { + + // Shift Left: https://codedost.com/c/arraypointers-in-c/c-program-shift-elements-array-left-direction/ + // Computationally heavier but doesn't need an entire leds2 array + + tmp = leds[XY16(0, y)]; + for (int m = 0; m < delta; m++) + { + // Do this delta time for each row... computationally expensive potentially. + for(int x = 0; x < VPANEL_W; x++) + { + leds[XY16(x, y)] = leds [XY16(x+1, y)]; + } + + leds[XY16(VPANEL_W-1, y)] = tmp; + } + + + /* + // Shift + for (int x = 0; x < VPANEL_W - delta; x++) { + leds2[XY(x, y)] = leds[XY(x + delta, y)]; + } + + // Wrap around + for (int x = VPANEL_W - delta; x < VPANEL_W; x++) { + leds2[XY(x, y)] = leds[XY(x + delta - VPANEL_W, y)]; + } + */ + } // end row loop + + /* + // write back to leds + for (uint8_t y = 0; y < VPANEL_H; y++) { + for (uint8_t x = 0; x < VPANEL_W; x++) { + leds[XY(x, y)] = leds2[XY(x, y)]; + } + } + */ + } + + void MoveY(byte delta) + { + + CRGB tmp = 0; + for (int x = 0; x < VPANEL_W; x++) + { + tmp = leds[XY16(x, 0)]; + for (int m = 0; m < delta; m++) // moves + { + // Do this delta time for each row... computationally expensive potentially. + for(int y = 0; y < VPANEL_H; y++) + { + leds[XY16(x, y)] = leds [XY16(x, y+1)]; + } + + leds[XY16(x, VPANEL_H-1)] = tmp; + } + } // end column loop + } /// MoveY + + +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Geometry.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Geometry.h new file mode 100644 index 0000000..4e47557 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Geometry.h @@ -0,0 +1,150 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from Noel Bundy's work: https://github.com/TwystNeko/Object3d + * Copyright (c) 2014 Noel Bundy + * + * Portions of this code are adapted from the Petty library: https://code.google.com/p/peggy/ + * Copyright (c) 2008 Windell H Oskay. All right reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Geometry_H +#define Geometry_H + +struct Vertex +{ + float x, y, z; + Vertex() + { + this->set(0, 0, 0); + } + + Vertex(float x, float y, float z) + { + this->set(x, y, z); + } + + void set(float x, float y, float z) + { + this->x = x; + this->y = y; + this->z = z; + } +}; + +struct EdgePoint +{ + int x, y; + boolean visible; + + EdgePoint() + { + this->set(0, 0); + this->visible = false; + } + + void set(int a, int b) + { + this->x = a; + this->y = b; + } +}; + +struct Point +{ + float x, y; + + Point() + { + set(0, 0); + } + + Point(float x, float y) + { + set(x, y); + } + + void set(float x, float y) + { + this->x = x; + this->y = y; + } + +}; + +struct squareFace +{ + int length; + int sommets[4]; + int ed[4]; + + squareFace() + { + set(-1, -1, -1, -1); + } + + squareFace(int a, int b, int c, int d) + { + this->length = 4; + this->sommets[0] = a; + this->sommets[1] = b; + this->sommets[2] = c; + this->sommets[3] = d; + } + + void set(int a, int b, int c, int d) + { + this->length = 4; + this->sommets[0] = a; + this->sommets[1] = b; + this->sommets[2] = c; + this->sommets[3] = d; + } + +}; + +struct triFace +{ + int length; + int sommets[3]; + int ed[3]; + + triFace() + { + set(-1,-1,-1); + } + triFace(int a, int b, int c) + { + this->length =3; + this->sommets[0]=a; + this->sommets[1]=b; + this->sommets[2]=c; + } + void set(int a, int b, int c) + { + this->length =3; + this->sommets[0]=a; + this->sommets[1]=b; + this->sommets[2]=c; + } +}; + +#endif \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PaletteFireKoz.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PaletteFireKoz.h new file mode 100644 index 0000000..86799ee --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PaletteFireKoz.h @@ -0,0 +1,3 @@ +const uint8_t PROGMEM palette_fire[] = {/* RGB888 R,G,B,R,G,B,R,G,B,... */ +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x05,0x00,0x00,0x0a,0x00,0x00,0x10,0x00,0x00,0x15,0x00,0x00,0x1b,0x00,0x00,0x20,0x00,0x00,0x25,0x00,0x00,0x2b,0x00,0x00,0x31,0x00,0x00,0x36,0x00,0x00,0x3c,0x00,0x00,0x41,0x00,0x00,0x46,0x00,0x00,0x4c,0x00,0x00,0x52,0x00,0x00,0x57,0x00,0x00,0x5d,0x00,0x00,0x62,0x00,0x00,0x68,0x00,0x00,0x6d,0x00,0x00,0x73,0x00,0x00,0x79,0x00,0x00,0x7e,0x00,0x00,0x83,0x00,0x00,0x89,0x00,0x00,0x8e,0x00,0x00,0x94,0x00,0x00,0x9a,0x00,0x00,0x9f,0x00,0x00,0xa5,0x00,0x00,0xaa,0x00,0x00,0xb0,0x00,0x00,0xb5,0x00,0x00,0xbb,0x00,0x00,0xc0,0x00,0x00,0xc6,0x00,0x00,0xcb,0x00,0x00,0xd1,0x00,0x00,0xd7,0x00,0x00,0xdc,0x00,0x00,0xe1,0x00,0x00,0xe6,0x00,0x00,0xe8,0x02,0x00,0xe9,0x08,0x00,0xe9,0x0f,0x00,0xe9,0x13,0x00,0xe9,0x16,0x00,0xe9,0x1b,0x00,0xe9,0x21,0x00,0xe9,0x26,0x00,0xe9,0x2a,0x00,0xe9,0x2e,0x00,0xe9,0x32,0x00,0xe9,0x37,0x00,0xe9,0x3b,0x00,0xe9,0x3f,0x00,0xe9,0x44,0x00,0xe9,0x4a,0x00,0xe9,0x4e,0x00,0xe9,0x52,0x00,0xe9,0x56,0x00,0xe9,0x5a,0x00,0xe9,0x5d,0x00,0xe9,0x63,0x00,0xe9,0x67,0x00,0xe9,0x6b,0x00,0xe9,0x71,0x00,0xe9,0x77,0x00,0xe9,0x78,0x00,0xe9,0x7c,0x00,0xe9,0x81,0x00,0xe9,0x86,0x00,0xe9,0x8b,0x00,0xe9,0x8f,0x00,0xe9,0x93,0x00,0xe9,0x99,0x00,0xe9,0x9d,0x00,0xe9,0xa0,0x00,0xe9,0xa4,0x00,0xe9,0xaa,0x00,0xe9,0xb0,0x00,0xe9,0xb4,0x00,0xe9,0xb5,0x00,0xe9,0xb9,0x00,0xe9,0xbe,0x00,0xe9,0xc3,0x00,0xe9,0xc9,0x00,0xe9,0xce,0x00,0xe9,0xd2,0x00,0xe9,0xd6,0x00,0xe9,0xd9,0x00,0xe9,0xdd,0x00,0xe9,0xe2,0x00,0xe9,0xe7,0x02,0xe9,0xe9,0x0e,0xe9,0xe9,0x1c,0xe9,0xe9,0x28,0xe9,0xe9,0x38,0xe9,0xe9,0x48,0xe9,0xe9,0x57,0xe9,0xe9,0x67,0xe9,0xe9,0x73,0xe9,0xe9,0x81,0xe9,0xe9,0x90,0xe9,0xe9,0xa1,0xe9,0xe9,0xb1,0xe9,0xe9,0xbf,0xe9,0xe9,0xcb,0xe9,0xe9,0xcb,0xe9,0xe9,0xcd,0xe9,0xe9,0xd9,0xe9,0xe9,0xe5,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe7,0xe7,0xe7,0xe7,0xe7,0xe7,0xe6,0xe6,0xe6,0xe4,0xe4,0xe4,0xe3,0xe3,0xe3,0xe0,0xe0,0xe0,0xdc,0xdc,0xdc,0xd8,0xd8,0xd8,0xd2,0xd2,0xd2,0xca,0xca,0xca,0xc1,0xc1,0xc1,0xb7,0xb7,0xb7,0xab,0xab,0xab,0x9d,0x9d,0x9d,0x8f,0x8f,0x8f,0x81,0x81,0x81,0x72,0x72,0x72,0x64,0x64,0x64,0x56,0x56,0x56,0x4a,0x4a,0x4a,0x3e,0x3e,0x3e,0x33,0x33,0x33,0x2a,0x2a,0x2a,0x22,0x22,0x22,0x1b,0x1b,0x1b,0x16,0x16,0x16,0x11,0x11,0x11,0x0d,0x0d,0x0d,0x0b,0x0b,0x0b,0x08,0x08,0x08,0x07,0x07,0x07,0x06,0x06,0x06,0x05,0x05,0x05,0x04,0x04,0x04,0x03,0x03,0x03,0x03,0x03,0x03,0x02,0x02,0x02,0x02,0x02,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternAttract.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternAttract.h new file mode 100644 index 0000000..dcb6491 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternAttract.h @@ -0,0 +1,74 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternAttract_H + +class PatternAttract : public Drawable { +private: + const int count = 8; + Attractor attractor; + +public: + PatternAttract() { + name = (char *)"Attract"; + } + + void start() { + int direction = random(0, 2); + if (direction == 0) + direction = -1; + + for (int i = 0; i < count; i++) { + Boid boid = Boid(15, 31 - i); + boid.mass = 1; // random(0.1, 2); + boid.velocity.x = ((float) random(40, 50)) / 100.0; + boid.velocity.x *= direction; + boid.velocity.y = 0; + boid.colorIndex = i * 32; + boids[i] = boid; + //dim = random(170, 250); + } + } + + unsigned int drawFrame() { + // dim all pixels on the display + uint8_t dim = beatsin8(2, 170, 250); + effects.DimAll(dim); + + for (int i = 0; i < count; i++) { + Boid boid = boids[i]; + + PVector force = attractor.attract(boid); + boid.applyForce(force); + + boid.update(); + effects.drawBackgroundFastLEDPixelCRGB(boid.location.x, boid.location.y, effects.ColorFromCurrentPalette(boid.colorIndex)); + + boids[i] = boid; + } + + effects.ShowFrame(); + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternBounce.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternBounce.h new file mode 100644 index 0000000..c0d595a --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternBounce.h @@ -0,0 +1,73 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternBounce_H + +class PatternBounce : public Drawable { +private: + static const int count = 32; + PVector gravity = PVector(0, 0.0125); + +public: + PatternBounce() { + name = (char *)"Bounce"; + } + + void start() { + unsigned int colorWidth = 256 / count; + for (int i = 0; i < count; i++) { + Boid boid = Boid(i, 0); + boid.velocity.x = 0; + boid.velocity.y = i * -0.01; + boid.colorIndex = colorWidth * i; + boid.maxforce = 10; + boid.maxspeed = 10; + boids[i] = boid; + } + } + + unsigned int drawFrame() { + // dim all pixels on the display + effects.DimAll(170); effects.ShowFrame(); + + for (int i = 0; i < count; i++) { + Boid boid = boids[i]; + + boid.applyForce(gravity); + + boid.update(); + + effects.drawBackgroundFastLEDPixelCRGB(boid.location.x, boid.location.y, effects.ColorFromCurrentPalette(boid.colorIndex)); + + if (boid.location.y >= VPANEL_H - 1) { + boid.location.y = VPANEL_H - 1; + boid.velocity.y *= -1.0; + } + + boids[i] = boid; + } + + return 15; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternCube.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternCube.h new file mode 100644 index 0000000..bebf5c8 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternCube.h @@ -0,0 +1,219 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from Noel Bundy's work: https://github.com/TwystNeko/Object3d + * Copyright (c) 2014 Noel Bundy + * + * Portions of this code are adapted from the Petty library: https://code.google.com/p/peggy/ + * Copyright (c) 2008 Windell H Oskay. All right reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternCube_H +#define PatternCube_H + +class PatternCube : public Drawable { + private: + float focal = 30; // Focal of the camera + int cubeWidth = 28; // Cube size + float Angx = 20.0, AngxSpeed = 0.05; // rotation (angle+speed) around X-axis + float Angy = 10.0, AngySpeed = 0.05; // rotation (angle+speed) around Y-axis + float Ox = 15.5, Oy = 15.5; // position (x,y) of the frame center + int zCamera = 110; // distance from cube to the eye of the camera + + // Local vertices + Vertex local[8]; + // Camera aligned vertices + Vertex aligned[8]; + // On-screen projected vertices + Point screen[8]; + // Faces + squareFace face[6]; + // Edges + EdgePoint edge[12]; + int nbEdges; + // ModelView matrix + float m00, m01, m02, m10, m11, m12, m20, m21, m22; + + // constructs the cube + void make(int w) + { + nbEdges = 0; + + local[0].set(-w, w, w); + local[1].set(w, w, w); + local[2].set(w, -w, w); + local[3].set(-w, -w, w); + local[4].set(-w, w, -w); + local[5].set(w, w, -w); + local[6].set(w, -w, -w); + local[7].set(-w, -w, -w); + + face[0].set(1, 0, 3, 2); + face[1].set(0, 4, 7, 3); + face[2].set(4, 0, 1, 5); + face[3].set(4, 5, 6, 7); + face[4].set(1, 2, 6, 5); + face[5].set(2, 3, 7, 6); + + int f, i; + for (f = 0; f < 6; f++) + { + for (i = 0; i < face[f].length; i++) + { + face[f].ed[i] = this->findEdge(face[f].sommets[i], face[f].sommets[i ? i - 1 : face[f].length - 1]); + } + } + } + + // finds edges from faces + int findEdge(int a, int b) + { + int i; + for (i = 0; i < nbEdges; i++) + if ((edge[i].x == a && edge[i].y == b) || (edge[i].x == b && edge[i].y == a)) + return i; + edge[nbEdges++].set(a, b); + return i; + } + + // rotates according to angle x&y + void rotate(float angx, float angy) + { + int i; + float cx = cos(angx); + float sx = sin(angx); + float cy = cos(angy); + float sy = sin(angy); + + m00 = cy; + m01 = 0; + m02 = -sy; + m10 = sx * sy; + m11 = cx; + m12 = sx * cy; + m20 = cx * sy; + m21 = -sx; + m22 = cx * cy; + + for (i = 0; i < 8; i++) + { + aligned[i].x = m00 * local[i].x + m01 * local[i].y + m02 * local[i].z; + aligned[i].y = m10 * local[i].x + m11 * local[i].y + m12 * local[i].z; + aligned[i].z = m20 * local[i].x + m21 * local[i].y + m22 * local[i].z + zCamera; + + screen[i].x = floor((Ox + focal * aligned[i].x / aligned[i].z)); + screen[i].y = floor((Oy - focal * aligned[i].y / aligned[i].z)); + } + + for (i = 0; i < 12; i++) + edge[i].visible = false; + + Point *pa, *pb, *pc; + for (i = 0; i < 6; i++) + { + pa = screen + face[i].sommets[0]; + pb = screen + face[i].sommets[1]; + pc = screen + face[i].sommets[2]; + + boolean back = ((pb->x - pa->x) * (pc->y - pa->y) - (pb->y - pa->y) * (pc->x - pa->x)) < 0; + if (!back) + { + int j; + for (j = 0; j < 4; j++) + { + edge[face[i].ed[j]].visible = true; + } + } + } + } + + byte hue = 0; + int step = 0; + + public: + PatternCube() { + name = (char *)"Cube"; + make(cubeWidth); + } + + unsigned int drawFrame() { + uint8_t blurAmount = beatsin8(2, 10, 255); + +#if FASTLED_VERSION >= 3001000 + blur2d(effects.leds, VPANEL_W, VPANEL_H, blurAmount); +#else + effects.DimAll(blurAmount); effects.ShowFrame(); +#endif + + zCamera = beatsin8(2, 100, 140); + AngxSpeed = beatsin8(3, 1, 10) / 100.0f; + AngySpeed = beatcos8(5, 1, 10) / 100.0f; + + // Update values + Angx += AngxSpeed; + Angy += AngySpeed; + if (Angx >= TWO_PI) + Angx -= TWO_PI; + if (Angy >= TWO_PI) + Angy -= TWO_PI; + + rotate(Angx, Angy); + + // Draw cube + int i; + + CRGB color = effects.ColorFromCurrentPalette(hue, 128); + + // Backface + EdgePoint *e; + for (i = 0; i < 12; i++) + { + e = edge + i; + if (!e->visible) { + matrix.drawLine(screen[e->x].x, screen[e->x].y, screen[e->y].x, screen[e->y].y, color); + } + } + + color = effects.ColorFromCurrentPalette(hue, 255); + + // Frontface + for (i = 0; i < 12; i++) + { + e = edge + i; + if (e->visible) + { + matrix.drawLine(screen[e->x].x, screen[e->x].y, screen[e->y].x, screen[e->y].y, color); + } + } + + step++; + if (step == 8) { + step = 0; + hue++; + } + + effects.ShowFrame(); + + return 20; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternElectricMandala.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternElectricMandala.h new file mode 100644 index 0000000..1977a83 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternElectricMandala.h @@ -0,0 +1,116 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Funky Noise" by Stefan Petrick: https://github.com/StefanPetrick/FunkyNoise + * Copyright (c) 2014 Stefan Petrick + * http://www.stefan-petrick.de/wordpress_beta + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternElectricMandala_H + +class PatternElectricMandala : public Drawable { + private: + + // The coordinates for 16-bit noise spaces. +#define NUM_LAYERS 1 + + // used for the random based animations + int16_t dx; + int16_t dy; + int16_t dz; + int16_t dsx; + int16_t dsy; + + public: + PatternElectricMandala() { + name = (char *)"ElectricMandala"; + } + + void start() { + // set to reasonable values to avoid a black out + noisesmoothing = 200; + + // just any free input pin + //random16_add_entropy(analogRead(18)); + + // fill coordinates with random values + // set zoom levels + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + noise_scale_x = 6000; + noise_scale_y = 6000; + + // for the random movement + dx = random8(); + dy = random8(); + dz = random8(); + dsx = random8(); + dsy = random8(); + } + + unsigned int drawFrame() { +#if FASTLED_VERSION >= 3001000 + // a new parameter set every 15 seconds + EVERY_N_SECONDS(15) { + //SetupRandomPalette3(); + dy = random16(500) - 250; // random16(2000) - 1000 is pretty fast but works fine, too + dx = random16(500) - 250; + dz = random16(500) - 250; + noise_scale_x = random16(10000) + 2000; + noise_scale_y = random16(10000) + 2000; + } +#endif + + noise_y += dy; + noise_x += dx; + noise_z += dz; + + effects.FillNoise(); + ShowNoiseLayer(0, 1, 0); + + effects.Caleidoscope3(); + effects.Caleidoscope1(); + + effects.ShowFrame(); + + return 30; + } + + // show just one layer + void ShowNoiseLayer(byte layer, byte colorrepeat, byte colorshift) { + for (uint16_t i = 0; i < VPANEL_W; i++) { + for (uint16_t j = 0; j < VPANEL_H; j++) { + + uint8_t color = noise[i][j]; + + uint8_t bri = color; + + // assign a color depending on the actual palette + CRGB pixel = ColorFromPalette(effects.currentPalette, colorrepeat * (color + colorshift), bri); + + effects.leds[XY16(i, j)] = pixel; + } + } + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFire.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFire.h new file mode 100644 index 0000000..731aff9 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFire.h @@ -0,0 +1,118 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/tree/master/examples/Fire2012WithPalette + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternFire_H +#define PatternFire_H + +#ifndef Effects_H +#include "Effects.h" +#endif + +class PatternFire : public Drawable { + private: + + public: + PatternFire() { + name = (char *)"Fire"; + } + + // There are two main parameters you can play with to control the look and + // feel of your fire: COOLING (used in step 1 above), and SPARKING (used + // in step 3 above). + // + // cooling: How much does the air cool as it rises? + // Less cooling = taller flames. More cooling = shorter flames. + // Default 55, suggested range 20-100 + int cooling = 100; + + // sparking: What chance (out of 255) is there that a new spark will be lit? + // Higher chance = more roaring fire. Lower chance = more flickery fire. + // Default 120, suggested range 50-200. + unsigned int sparking = 100; + + unsigned int drawFrame() { + // Add entropy to random number generator; we use a lot of it. + random16_add_entropy( random16()); + + effects.DimAll(235); + + for (int x = 0; x < VPANEL_W; x++) { + // Step 1. Cool down every cell a little + for (int y = 0; y < VPANEL_H; y++) { + int xy = XY(x, y); + heat[xy] = qsub8(heat[xy], random8(0, ((cooling * 10) / VPANEL_H) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int y = 0; y < VPANEL_H; y++) { + heat[XY(x, y)] = (heat[XY(x, y + 1)] + heat[XY(x, y + 2)] + heat[XY(x, y + 2)]) / 3; + } + + // Step 2. Randomly ignite new 'sparks' of heat + if (random8() < sparking) { + // int x = (p[0] + p[1] + p[2]) / 3; + + int xy = XY(x, VPANEL_H - 1); + heat[xy] = qadd8(heat[xy], random8(160, 255)); + } + + // Step 4. Map from heat cells to LED colors + for (int y = 0; y < VPANEL_H; y++) { + int xy = XY(x, y); + byte colorIndex = heat[xy]; + + // Recommend that you use values 0-240 rather than + // the usual 0-255, as the last 15 colors will be + // 'wrapping around' from the hot end to the cold end, + // which looks wrong. + colorIndex = scale8(colorIndex, 200); + + // override color 0 to ensure a black background? + if (colorIndex != 0) + // effects.leds[xy] = CRGB::Black; + // else + effects.leds[xy] = effects.ColorFromCurrentPalette(colorIndex); + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(2); + effects.MoveFractionalNoiseX(2); + + + effects.ShowFrame(); + + return 15; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFireKoz.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFireKoz.h new file mode 100644 index 0000000..c553d6b --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFireKoz.h @@ -0,0 +1,109 @@ +/* + Aurora: https://github.com/pixelmatix/aurora + Copyright (c) 2014 Jason Coon + + Added by @Kosso. Cobbled together from various places which I can't remember. I'll update this when I track it down. + Requires PaletteFireKoz.h + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternFireKoz_H +#define PatternFireKoz_H + +class PatternFireKoz : public Drawable { + private: + + const int FIRE_HEIGHT = 800; + + int Bit = 0, NBit = 1; + float fire_c; + // might not need this buffer here... there some led buffers set up in Effects.h + uint8_t fireBuffer[VPANEL_W][VPANEL_H][2]; + + public: + PatternFireKoz() { + name = (char *)"FireKoz"; + } + + unsigned int drawFrame() { + + for (int x = 1; x < VPANEL_W - 1; x++) + { + fireBuffer[x][VPANEL_H - 2][Bit] = random(0, FIRE_HEIGHT); + if (random(0, 100) > 80) + { + fireBuffer[x][VPANEL_H - 2][Bit] = 0; + fireBuffer[x][VPANEL_H - 3][Bit] = 0; + } + } + for (int y = 1; y < VPANEL_H - 1; y++) + { + for (int x = 1; x < VPANEL_W - 1; x++) + { + fire_c = (fireBuffer[x - 1][y][Bit] + + fireBuffer[x + 1][y][Bit] + + fireBuffer[x][y - 1][Bit] + + fireBuffer[x][y + 1][Bit] + + fireBuffer[x][y][Bit]) / + 5.0; + + fire_c = (fireBuffer[x - 1][y][Bit] + + fireBuffer[x + 1][y][Bit] + + fireBuffer[x][y - 1][Bit] + + fireBuffer[x][y + 1][Bit] + + fireBuffer[x][y][Bit]) / + 5.0; + + if (fire_c > (FIRE_HEIGHT / 2) && fire_c < FIRE_HEIGHT) { + fire_c -= 0.2; + } else if (fire_c > (FIRE_HEIGHT / 4) && fire_c < (FIRE_HEIGHT / 2)) { + fire_c -= 0.4; + } else if (fire_c <= (FIRE_HEIGHT / 8)) { + fire_c -= 0.7; + } else { + fire_c -= 1; + } + if (fire_c < 0) + fire_c = 0; + if (fire_c >= FIRE_HEIGHT + 1) + fire_c = FIRE_HEIGHT - 1; + fireBuffer[x][y - 1][NBit] = fire_c; + int index = (int)fire_c * 3; + if (fire_c == 0) + { + effects.drawBackgroundFastLEDPixelCRGB(x, y, CRGB(0, 0, 0)); + } + else + { + effects.drawBackgroundFastLEDPixelCRGB(x, y, CRGB(palette_fire[index], palette_fire[index + 1], palette_fire[index + 2])); + } + } + } + //display.drawRect(0, 0, VPANEL_W, VPANEL_H, display.color565(25, 25, 25)); + + NBit = Bit; + Bit = 1 - Bit; + + effects.ShowFrame(); + + return 30; // no idea what this is for... + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFlock.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFlock.h new file mode 100644 index 0000000..3ae31b1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFlock.h @@ -0,0 +1,125 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from "Flocking" in "The Nature of Code" by Daniel Shiffman: http://natureofcode.com/ + * Copyright (c) 2014 Daniel Shiffman + * http://www.shiffman.net + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Flocking +// Daniel Shiffman +// The Nature of Code, Spring 2009 + +// Demonstration of Craig Reynolds' "Flocking" behavior +// See: http://www.red3d.com/cwr/ +// Rules: Cohesion, Separation, Alignment + +#ifndef PatternFlock_H +#define PatternFlock_H + +class PatternFlock : public Drawable { + public: + PatternFlock() { + name = (char *)"Flock"; + } + + static const int boidCount = 10; + Boid predator; + + PVector wind; + byte hue = 0; + bool predatorPresent = true; + + void start() { + for (int i = 0; i < boidCount; i++) { + boids[i] = Boid(15, 15); + boids[i].maxspeed = 0.380; + boids[i].maxforce = 0.015; + } + + predatorPresent = random(0, 2) >= 1; + if (predatorPresent) { + predator = Boid(31, 31); + predatorPresent = true; + predator.maxspeed = 0.385; + predator.maxforce = 0.020; + predator.neighbordist = 16.0; + predator.desiredseparation = 0.0; + } + } + + unsigned int drawFrame() { + effects.DimAll(230); effects.ShowFrame(); + + bool applyWind = random(0, 255) > 250; + if (applyWind) { + wind.x = Boid::randomf() * .015; + wind.y = Boid::randomf() * .015; + } + + CRGB color = effects.ColorFromCurrentPalette(hue); + + for (int i = 0; i < boidCount; i++) { + Boid * boid = &boids[i]; + + if (predatorPresent) { + // flee from predator + boid->repelForce(predator.location, 10); + } + + boid->run(boids, boidCount); + boid->wrapAroundBorders(); + PVector location = boid->location; + // PVector velocity = boid->velocity; + // backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color); + // effects.leds[XY(location.x, location.y)] += color; + effects.drawBackgroundFastLEDPixelCRGB(location.x, location.y, color); + + if (applyWind) { + boid->applyForce(wind); + applyWind = false; + } + } + + if (predatorPresent) { + predator.run(boids, boidCount); + predator.wrapAroundBorders(); + color = effects.ColorFromCurrentPalette(hue + 128); + PVector location = predator.location; + // PVector velocity = predator.velocity; + // backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color); + // effects.leds[XY(location.x, location.y)] += color; + effects.drawBackgroundFastLEDPixelCRGB(location.x, location.y, color); + } + + EVERY_N_MILLIS(200) { + hue++; + } + + EVERY_N_SECONDS(30) { + predatorPresent = !predatorPresent; + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFlowField.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFlowField.h new file mode 100644 index 0000000..be91ff2 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternFlowField.h @@ -0,0 +1,92 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternFlowField_H + +class PatternFlowField : public Drawable { + public: + PatternFlowField() { + name = (char *)"FlowField"; + } + + uint16_t x; + uint16_t y; + uint16_t z; + + uint16_t speed = 1; + uint16_t scale = 26; + + static const int count = 40; + + byte hue = 0; + + void start() { + x = random16(); + y = random16(); + z = random16(); + + for (int i = 0; i < count; i++) { + boids[i] = Boid(random(VPANEL_W), 0); + } + } + + unsigned int drawFrame() { + effects.DimAll(240); + + // CRGB color = effects.ColorFromCurrentPalette(hue); + + for (int i = 0; i < count; i++) { + Boid * boid = &boids[i]; + + int ioffset = scale * boid->location.x; + int joffset = scale * boid->location.y; + + byte angle = inoise8(x + ioffset, y + joffset, z); + + boid->velocity.x = (float) sin8(angle) * 0.0078125 - 1.0; + boid->velocity.y = -((float)cos8(angle) * 0.0078125 - 1.0); + boid->update(); + + effects.drawBackgroundFastLEDPixelCRGB(boid->location.x, boid->location.y, effects.ColorFromCurrentPalette(angle + hue)); // color + + if (boid->location.x < 0 || boid->location.x >= VPANEL_W || + boid->location.y < 0 || boid->location.y >= VPANEL_H) { + boid->location.x = random(VPANEL_W); + boid->location.y = 0; + } + } + + EVERY_N_MILLIS(200) { + hue++; + } + + x += speed; + y += speed; + z += speed; + + effects.ShowFrame(); + + return 50; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternIncrementalDrift.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternIncrementalDrift.h new file mode 100644 index 0000000..573c765 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternIncrementalDrift.h @@ -0,0 +1,51 @@ +/* +* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternIncrementalDrift_H +#define PatternIncrementalDrift_H + +class PatternIncrementalDrift : public Drawable { + public: + PatternIncrementalDrift() { + name = (char *)"Incremental Drift"; + } + + unsigned int drawFrame() { + uint8_t dim = beatsin8(2, 230, 250); + effects.DimAll(dim); effects.ShowFrame(); + + for (int i = 2; i <= VPANEL_W / 2; i++) + { + CRGB color = effects.ColorFromCurrentPalette((i - 2) * (240 / (VPANEL_W / 2))); + + uint8_t x = beatcos8((17 - i) * 2, MATRIX_CENTER_X - i, MATRIX_CENTER_X + i); + uint8_t y = beatsin8((17 - i) * 2, MATRIX_CENTER_Y - i, MATRIX_CENTER_Y + i); + + effects.drawBackgroundFastLEDPixelCRGB(x, y, color); + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternIncrementalDrift2.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternIncrementalDrift2.h new file mode 100644 index 0000000..03312f6 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternIncrementalDrift2.h @@ -0,0 +1,64 @@ +/* +* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternIncrementalDrift2_H +#define PatternIncrementalDrift2_H + +class PatternIncrementalDrift2 : public Drawable { + public: + PatternIncrementalDrift2() { + name = (char *)"Incremental Drift Rose"; + } + + unsigned int drawFrame() { + uint8_t dim = beatsin8(2, 170, 250); + effects.DimAll(dim); effects.ShowFrame(); + + for (int i = 2; i < VPANEL_H / 2; ++i) + //for (uint8_t i = 0; i < 32; i++) + { + CRGB color; + + uint8_t x = 0; + uint8_t y = 0; + + if (i < 16) { + x = beatcos8((i + 1) * 2, i, VPANEL_W - i); + y = beatsin8((i + 1) * 2, i, VPANEL_H - i); + color = effects.ColorFromCurrentPalette(i * 14); + } + else + { + x = beatsin8((32 - i) * 2, VPANEL_W - i, i + 1); + y = beatcos8((32 - i) * 2, VPANEL_H - i, i + 1); + color = effects.ColorFromCurrentPalette((31 - i) * 14); + } + + effects.drawBackgroundFastLEDPixelCRGB(x, y, color); + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternInfinity.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternInfinity.h new file mode 100644 index 0000000..c99f329 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternInfinity.h @@ -0,0 +1,61 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternInfinity_H + +class PatternInfinity : public Drawable { +public: + PatternInfinity() { + name = (char *)"Infinity"; + } + + unsigned int drawFrame() { + // dim all pixels on the display slightly + // to 250/255 (98%) of their current brightness + blur2d(effects.leds, VPANEL_W > 255 ? 255 : VPANEL_W, VPANEL_H > 255 ? 255 : VPANEL_H, 250); + // effects.DimAll(250); effects.ShowFrame(); + + + // the Effects class has some sample oscillators + // that move from 0 to 255 at different speeds + effects.MoveOscillators(); + + // the horizontal position of the head of the infinity sign + // oscillates from 0 to the maximum horizontal and back + int x = (VPANEL_W - 1) - effects.p[1]; + + // the vertical position of the head oscillates + // from 8 to 23 and back (hard-coded for a 32x32 matrix) + int y = map8(sin8(effects.osci[3]), 8, 23); + + // the hue oscillates from 0 to 255, overflowing back to 0 + byte hue = sin8(effects.osci[5]); + + // draw a pixel at x,y using a color from the current palette + effects.Pixel(x, y, hue); + + effects.ShowFrame(); + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternInvaders.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternInvaders.h new file mode 100644 index 0000000..98f3200 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternInvaders.h @@ -0,0 +1,154 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Inspired by 'Space Invader Generator': https://the8bitpimp.wordpress.com/2013/05/07/space-invader-generator + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternInvaders_H +#define PatternInvaders_H + +class PatternInvadersSmall : public Drawable { + private: + uint8_t x = 1; + uint8_t y = 1; + + public: + PatternInvadersSmall() { + name = (char *)"Invaders Small"; + } + + void start() { + matrix->fillScreen(0); + } + + unsigned int drawFrame() { + CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255)); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 5; j++) { + CRGB color = CRGB::Black; + + if (random(0, 2) == 1) color = color1; + + effects.drawBackgroundFastLEDPixelCRGB(x + i, y + j, color); + + if (i < 2) + effects.drawBackgroundFastLEDPixelCRGB(x + (4 - i), y + j, color); + } + } + + x += 6; + if (x > 25) { + x = 1; + y += 6; + } + + if (y > 25) y = x = 1; + + effects.ShowFrame(); + + return 125; + } +}; + +class PatternInvadersMedium : public Drawable { + private: + uint8_t x = 0; + uint8_t y = 0; + + public: + PatternInvadersMedium() { + name = (char *)"Invaders Medium"; + } + + void start() { + matrix->fillScreen(0); + } + + unsigned int drawFrame() { + CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255)); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 5; j++) { + CRGB color = CRGB::Black; + + if (random(0, 2) == 1) color = color1; + + matrix->fillRect(x + (i * 2), y + (j * 2), x + (i * 2 + 1), y + (j * 2 + 1), color); + + if (i < 2) + matrix->fillRect(x + (8 - i * 2), y + (j * 2), x + (9 - i * 2), y + (j * 2 + 1), color); + } + } + + x += 11; + if (x > 22) { + x = 0; + y += 11; + } + + if (y > 22) y = x = 0; + + effects.ShowFrame(); + + return 500; + } +}; + +class PatternInvadersLarge : public Drawable { + private: + + public: + PatternInvadersLarge() { + name = (char *)"Invaders Large"; + } + + void start() { + matrix->fillScreen(0); + } + + unsigned int drawFrame() { + matrix->fillScreen(0); + + CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255)); + + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 5; y++) { + CRGB color = CRGB::Black; + + if (random(0, 2) == 1) { + color = color1; + } + + matrix->fillRect(1 + x * 6, 1 + y * 6, 5 + x * 6, 5 + y * 6, color); + + if (x < 2) + matrix->fillRect(1 + (4 - x) * 6, 1 + y * 6, 5 + (4 - x) * 6, 5 + y * 6, color); + } + } + + effects.ShowFrame(); + + return 2000; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternLife.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternLife.h new file mode 100644 index 0000000..8074e40 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternLife.h @@ -0,0 +1,129 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from Andrew: http://pastebin.com/f22bfe94d + * which, in turn, was "Adapted from the Life example on the Processing.org site" + * + * Made much more colorful by J.B. Langston: https://github.com/jblang/aurora/commit/6db5a884e3df5d686445c4f6b669f1668841929b + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternLife_H +#define PatternLife_H + +class Cell { +public: + byte alive : 1; + byte prev : 1; + byte hue: 6; + byte brightness; +}; + +class PatternLife : public Drawable { +private: + Cell world[VPANEL_W][VPANEL_H]; + unsigned int density = 50; + int generation = 0; + + void randomFillWorld() { + for (int i = 0; i < VPANEL_W; i++) { + for (int j = 0; j < VPANEL_H; j++) { + if (random(100) < density) { + world[i][j].alive = 1; + world[i][j].brightness = 255; + } + else { + world[i][j].alive = 0; + world[i][j].brightness = 0; + } + world[i][j].prev = world[i][j].alive; + world[i][j].hue = 0; + } + } + } + + int neighbours(int x, int y) { + return (world[(x + 1) % VPANEL_W][y].prev) + + (world[x][(y + 1) % VPANEL_H].prev) + + (world[(x + VPANEL_W - 1) % VPANEL_W][y].prev) + + (world[x][(y + VPANEL_H - 1) % VPANEL_H].prev) + + (world[(x + 1) % VPANEL_W][(y + 1) % VPANEL_H].prev) + + (world[(x + VPANEL_W - 1) % VPANEL_W][(y + 1) % VPANEL_H].prev) + + (world[(x + VPANEL_W - 1) % VPANEL_W][(y + VPANEL_H - 1) % VPANEL_H].prev) + + (world[(x + 1) % VPANEL_W][(y + VPANEL_H - 1) % VPANEL_H].prev); + } + +public: + PatternLife() { + name = (char *)"Life"; + } + + unsigned int drawFrame() { + if (generation == 0) { + effects.ClearFrame(); + + randomFillWorld(); + } + + // Display current generation + for (int i = 0; i < VPANEL_W; i++) { + for (int j = 0; j < VPANEL_H; j++) { + effects.leds[XY(i, j)] = effects.ColorFromCurrentPalette(world[i][j].hue * 4, world[i][j].brightness); + } + } + + // Birth and death cycle + for (int x = 0; x < VPANEL_W; x++) { + for (int y = 0; y < VPANEL_H; y++) { + // Default is for cell to stay the same + if (world[x][y].brightness > 0 && world[x][y].prev == 0) + world[x][y].brightness *= 0.9; + int count = neighbours(x, y); + if (count == 3 && world[x][y].prev == 0) { + // A new cell is born + world[x][y].alive = 1; + world[x][y].hue += 2; + world[x][y].brightness = 255; + } else if ((count < 2 || count > 3) && world[x][y].prev == 1) { + // Cell dies + world[x][y].alive = 0; + } + } + } + + // Copy next generation into place + for (int x = 0; x < VPANEL_W; x++) { + for (int y = 0; y < VPANEL_H; y++) { + world[x][y].prev = world[x][y].alive; + } + } + + + generation++; + if (generation >= 256) + generation = 0; + + effects.ShowFrame(); + + return 60; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternMaze.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternMaze.h new file mode 100644 index 0000000..b4e26a9 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternMaze.h @@ -0,0 +1,264 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Many thanks to Jamis Buck for the documentation of the Growing Tree maze generation algorithm: http://weblog.jamisbuck.org/2011/1/27/maze-generation-growing-tree-algorithm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternMaze_H +#define PatternMaze_H + +class PatternMaze : public Drawable { +private: + enum Directions { + None = 0, + Up = 1, + Down = 2, + Left = 4, + Right = 8, + }; + + struct Point{ + int x; + int y; + + static Point New(int x, int y) { + Point point; + point.x = x; + point.y = y; + return point; + } + + Point Move(Directions direction) { + switch (direction) + { + case Up: + return New(x, y - 1); + + case Down: + return New(x, y + 1); + + case Left: + return New(x - 1, y); + + case Right: + default: + return New(x + 1, y); + } + } + + static Directions Opposite(Directions direction) { + switch (direction) { + case Up: + return Down; + + case Down: + return Up; + + case Left: + return Right; + + case Right: + default: + return Left; + } + } + }; + +// int width = 16; +// int height = 16; + + static const int width = VPANEL_W / 2; + static const int height = VPANEL_H / 2; + + + Directions grid[width][height]; + + Point point; + + Point cells[256]; + int cellCount = 0; + + int algorithm = 0; + int algorithmCount = 1; + + byte hue = 0; + byte hueOffset = 0; + + Directions directions[4] = { Up, Down, Left, Right }; + + void removeCell(int index) {// shift cells after index down one + for (int i = index; i < cellCount - 1; i++) { + cells[i] = cells[i + 1]; + } + + cellCount--; + } + + void shuffleDirections() { + for (int a = 0; a < 4; a++) + { + int r = random(a, 4); + Directions temp = directions[a]; + directions[a] = directions[r]; + directions[r] = temp; + } + } + + Point createPoint(int x, int y) { + Point point; + point.x = x; + point.y = y; + return point; + } + + CRGB chooseColor(int index) { + byte h = index + hueOffset; + + switch (algorithm) { + case 0: + default: + return effects.ColorFromCurrentPalette(h); + + case 1: + return effects.ColorFromCurrentPalette(hue++); + } + } + + int chooseIndex(int max) { + switch (algorithm) { + case 0: + default: + // choose newest (recursive backtracker) + return max - 1; + + case 1: + // choose random(Prim's) + return random(max); + + // case 2: + // // choose oldest (not good, so disabling) + // return 0; + } + } + + void generateMaze() { + while (cellCount > 1) { + drawNextCell(); + } + } + + void drawNextCell() { + int index = chooseIndex(cellCount); + + if (index < 0) + return; + + point = cells[index]; + + Point imagePoint = createPoint(point.x * 2, point.y * 2); + + //effects.drawBackgroundFastLEDPixelCRGB(imagePoint.x, imagePoint.y, CRGB(CRGB::Gray)); + + shuffleDirections(); + + CRGB color = chooseColor(index); + + for (int i = 0; i < 4; i++) { + Directions direction = directions[i]; + + Point newPoint = point.Move(direction); + if (newPoint.x >= 0 && newPoint.y >= 0 && newPoint.x < width && newPoint.y < height && grid[newPoint.y][newPoint.x] == None) { + grid[point.y][point.x] = (Directions) ((int) grid[point.y][point.x] | (int) direction); + grid[newPoint.y][newPoint.x] = (Directions) ((int) grid[newPoint.y][newPoint.x] | (int) point.Opposite(direction)); + + Point newImagePoint = imagePoint.Move(direction); + + effects.drawBackgroundFastLEDPixelCRGB(newImagePoint.x, newImagePoint.y, color); + + cellCount++; + cells[cellCount - 1] = newPoint; + + index = -1; + break; + } + } + + if (index > -1) { + Point finishedPoint = cells[index]; + imagePoint = createPoint(finishedPoint.x * 2, finishedPoint.y * 2); + effects.drawBackgroundFastLEDPixelCRGB(imagePoint.x, imagePoint.y, color); + + removeCell(index); + } + } + +public: + PatternMaze() { + name = (char *)"Maze"; + } + + unsigned int drawFrame() { + if (cellCount < 1) { + + effects.ClearFrame(); + + // reset the maze grid + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + grid[y][x] = None; + } + } + + int x = random(width); + int y = random(height); + + cells[0] = createPoint(x, y); + + cellCount = 1; + + hue = 0; + hueOffset = random(0, 256); + + } + + drawNextCell(); + + if (cellCount < 1) { + algorithm++; + if (algorithm >= algorithmCount) + algorithm = 0; + + return 0; + } + + effects.ShowFrame(); + + return 0; + } + + void start() { + effects.ClearFrame(); + cellCount = 0; + hue = 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternMunch.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternMunch.h new file mode 100644 index 0000000..092dca1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternMunch.h @@ -0,0 +1,73 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Munch pattern created by J.B. Langston: https://github.com/jblang/aurora/blob/master/PatternMunch.h + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternMunch_H +#define PatternMunch_H + + +class PatternMunch : public Drawable { +private: + byte count = 0; + byte dir = 1; + byte flip = 0; + byte generation = 0; + +public: + PatternMunch() { + name = (char *)"Munch"; + } + + unsigned int drawFrame() { + + for (uint16_t x = 0; x < VPANEL_W; x++) { + for (uint16_t y = 0; y < VPANEL_H; y++) { + effects.leds[XY16(x, y)] = (x ^ y ^ flip) < count ? effects.ColorFromCurrentPalette(((x ^ y) << 2) + generation) : CRGB::Black; + + // The below is more pleasant + // effects.leds[XY(x, y)] = effects.ColorFromCurrentPalette(((x ^ y) << 2) + generation) ; + } + } + + count += dir; + + if (count <= 0 || count >= VPANEL_W) { + dir = -dir; + } + + if (count <= 0) { + if (flip == 0) + flip = VPANEL_W-1; + else + flip = 0; + } + + generation++; + + // show it ffs! + effects.ShowFrame(); + return 60; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternNoiseSmearing.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternNoiseSmearing.h new file mode 100644 index 0000000..2d6723a --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternNoiseSmearing.h @@ -0,0 +1,338 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Portions of this code are adapted from "Noise Smearing" by Stefan Petrick: https://gist.githubusercontent.com/embedded-creations/5cd47d83cb0e04f4574d/raw/ebf6a82b4755d55cfba3bf6598f7b19047f89daf/NoiseSmearing.ino +* Copyright (c) 2014 Stefan Petrick +* http://www.stefan-petrick.de/wordpress_beta +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternNoiseSmearing_H +#define PatternNoiseSmearing_H + +byte patternNoiseSmearingHue = 0; + +class PatternMultipleStream : public Drawable { +public: + PatternMultipleStream() { + name = (char *)"MultipleStream"; + } + + // this pattern draws two points to the screen based on sin/cos if a counter + // (comment out NoiseSmearWithRadius to see pattern of pixels) + // these pixels are smeared by a large radius, giving a lot of movement + // the image is dimmed before each drawing to not saturate the screen with color + // the smear has an offset so the pixels usually have a trail leading toward the upper left + unsigned int drawFrame() { + static unsigned long counter = 0; +#if 0 + // this counter lets put delays between each frame and still get the same animation + counter++; +#else + // this counter updates in real time and can't be slowed down for debugging + counter = millis() / 10; +#endif + + byte x1 = 4 + sin8(counter * 2) / 10; + byte x2 = 8 + sin8(counter * 2) / 16; + byte y2 = 8 + cos8((counter * 2) / 3) / 16; + + effects.leds[XY(x1, x2)] = effects.ColorFromCurrentPalette(patternNoiseSmearingHue); + effects.leds[XY(x2, y2)] = effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 128); + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(8); + effects.MoveFractionalNoiseX(); + + effects.MoveY(8); + effects.MoveFractionalNoiseY(); + + patternNoiseSmearingHue++; + + return 0; + } +}; + +class PatternMultipleStream2 : public Drawable { +public: + PatternMultipleStream2() { + name = (char *)"MultipleStream2"; + } + + unsigned int drawFrame() { + effects.DimAll(230); effects.ShowFrame(); + + byte xx = 4 + sin8(millis() / 9) / 10; + byte yy = 4 + cos8(millis() / 10) / 10; + effects.leds[XY(xx, yy)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue); + + xx = 8 + sin8(millis() / 10) / 16; + yy = 8 + cos8(millis() / 7) / 16; + effects.leds[XY(xx, yy)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 80); + + effects.leds[XY(15, 15)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 160); + + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + + patternNoiseSmearingHue++; + + return 0; + } +}; + +class PatternMultipleStream3 : public Drawable { +public: + PatternMultipleStream3() { + name = (char *)"MultipleStream3"; + } + + unsigned int drawFrame() { + //CLS(); + effects.DimAll(235); effects.ShowFrame(); + + for (uint8_t i = 3; i < 32; i = i + 4) { + effects.leds[XY(i, 15)] += effects.ColorFromCurrentPalette(i * 8); + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + + effects.ShowFrame(); + + return 1; + } +}; + +class PatternMultipleStream4 : public Drawable { +public: + PatternMultipleStream4() { + name = (char *)"MultipleStream4"; + } + + unsigned int drawFrame() { + + //CLS(); + effects.DimAll(235); effects.ShowFrame(); + + effects.leds[XY(15, 15)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue); + + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(8); + effects.MoveFractionalNoiseX(); + + effects.MoveY(8); + effects.MoveFractionalNoiseY(); + + patternNoiseSmearingHue++; + + return 0; + } +}; + +class PatternMultipleStream5 : public Drawable { +public: + PatternMultipleStream5() { + name = (char *)"MultipleStream5"; + } + + unsigned int drawFrame() { + + //CLS(); + effects.DimAll(235); effects.ShowFrame(); + + + for (uint8_t i = 3; i < 32; i = i + 4) { + effects.leds[XY(i, 31)] += effects.ColorFromCurrentPalette(i * 8); + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(4); + effects.MoveFractionalNoiseX(4); + + return 0; + } +}; + +class PatternMultipleStream8 : public Drawable { +public: + PatternMultipleStream8() { + name = (char *)"MultipleStream8"; + } + + unsigned int drawFrame() { + effects.DimAll(230); effects.ShowFrame(); + + // draw grid of rainbow dots on top of the dimmed image + for (uint8_t y = 1; y < 32; y = y + 6) { + for (uint8_t x = 1; x < 32; x = x + 6) { + + effects.leds[XY(x, y)] += effects.ColorFromCurrentPalette((x * y) / 4); + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseX(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseY(4); + + return 0; + } +}; + +class PatternPaletteSmear : public Drawable { +public: + PatternPaletteSmear() { + name = (char *)"PaletteSmear"; + } + + unsigned int drawFrame() { + + effects.DimAll(170); effects.ShowFrame(); + + // draw a rainbow color palette + for (uint8_t y = 0; y < VPANEL_H; y++) { + for (uint8_t x = 0; x < VPANEL_W; x++) { + effects.leds[XY(x, y)] += effects.ColorFromCurrentPalette(x * 8, y * 8 + 7); + } + } + + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + + effects.FillNoise(); + + effects.MoveX(3); + //effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + effects.ShowFrame(); + + return 0; + } +}; + +class PatternRainbowFlag : public Drawable { +public: + PatternRainbowFlag() { + name = (char *)"RainbowFlag"; + } + + unsigned int drawFrame() { + effects.DimAll(10); effects.ShowFrame(); + + CRGB rainbow[7] = { + CRGB::Red, + CRGB::Orange, + CRGB::Yellow, + CRGB::Green, + CRGB::Blue, + CRGB::Violet + }; + + uint8_t y = 2; + + for (uint8_t c = 0; c < 6; c++) { + for (uint8_t j = 0; j < 5; j++) { + for (uint8_t x = 0; x < VPANEL_W; x++) { + effects.leds[XY(x, y)] += rainbow[c]; + } + + y++; + if (y >= VPANEL_H) + break; + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseY(4); + + effects.MoveY(3); + effects.MoveFractionalNoiseX(4); + + return 0; + } +}; +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPendulumWave.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPendulumWave.h new file mode 100644 index 0000000..985e15a --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPendulumWave.h @@ -0,0 +1,66 @@ +/* +* +* Inspired by and based on a loading animation for Prismata by Lunarch Studios: +* http://www.reddit.com/r/gifs/comments/2on8si/connecting_to_server_so_mesmerizing/cmow0sz +* +* Lunarch Studios Inc. hereby publishes the Actionscript 3 source code pasted in this +* comment under the Creative Commons CC0 1.0 Universal Public Domain Dedication. +* Lunarch Studios Inc. waives all rights to the work worldwide under copyright law, +* including all related and neighboring rights, to the extent allowed by law. +* You can copy, modify, distribute and perform the work, even for commercial purposes, +* all without asking permission. +* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternPendulumWave_H +#define PatternPendulumWave_H + +#define WAVE_BPM 25 +#define AMP_BPM 2 +#define SKEW_BPM 4 +#define WAVE_TIMEMINSKEW VPANEL_W/8 +#define WAVE_TIMEMAXSKEW VPANEL_W/2 + +class PatternPendulumWave : public Drawable { + public: + PatternPendulumWave() { + name = (char *)"Pendulum Wave"; + } + + unsigned int drawFrame() { + effects.ClearFrame(); + + for (int x = 0; x < VPANEL_W; ++x) + { + uint16_t amp = beatsin16(AMP_BPM, VPANEL_H/8, VPANEL_H-1); + uint16_t offset = (VPANEL_H - beatsin16(AMP_BPM, 0, VPANEL_H))/2; + + uint8_t y = beatsin16(WAVE_BPM, 0, amp, x*beatsin16(SKEW_BPM, WAVE_TIMEMINSKEW, WAVE_TIMEMAXSKEW)) + offset; + + effects.drawBackgroundFastLEDPixelCRGB(x, y, effects.ColorFromCurrentPalette(x * 7)); + } + effects.ShowFrame(); + return 20; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPlasma.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPlasma.h new file mode 100644 index 0000000..dcecc97 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPlasma.h @@ -0,0 +1,66 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from LedEffects Plasma by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Plasma.cpp?at=default + * Copyright (c) 2013 Robert Atkins + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternPlasma_H +#define PatternPlasma_H + +class PatternPlasma : public Drawable { +private: + int time = 0; + int cycles = 0; + +public: + PatternPlasma() { + name = (char *)"Plasma"; + } + + unsigned int drawFrame() { + for (int x = 0; x < VPANEL_W; x++) { + for (int y = 0; y < VPANEL_H; y++) { + int16_t v = 0; + uint8_t wibble = sin8(time); + v += sin16(x * wibble * 6 + time); + v += cos16(y * (128 - wibble) * 6 + time); + v += sin16(y * x * cos8(-time) / 8); + + effects.Pixel(x, y, (v >> 8) + 127); + } + } + + time += 1; + cycles++; + + if (cycles >= 2048) { + time = 0; + cycles = 0; + } + + effects.ShowFrame(); + + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPulse.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPulse.h new file mode 100644 index 0000000..8992a4d --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternPulse.h @@ -0,0 +1,82 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Based at least in part on someone else's work that I can no longer find. + * Please let me know if you recognize any of this code! + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternPulse_H +#define PatternPulse_H + +class PatternPulse : public Drawable { + private: + int hue; + int centerX = 0; + int centerY = 0; + int step = -1; + int maxSteps = 16; + float fadeRate = 0.8; + int diff; + + public: + PatternPulse() { + name = (char *)"Pulse"; + } + + unsigned int drawFrame() { + effects.DimAll(235); + + if (step == -1) { + centerX = random(32); + centerY = random(32); + hue = random(256); // 170; + step = 0; + } + + if (step == 0) { + matrix.drawCircle(centerX, centerY, step, effects.ColorFromCurrentPalette(hue)); + step++; + } + else { + if (step < maxSteps) { + // initial pulse + matrix.drawCircle(centerX, centerY, step, effects.ColorFromCurrentPalette(hue, pow(fadeRate, step - 2) * 255)); + + // secondary pulse + if (step > 3) { + matrix.drawCircle(centerX, centerY, step - 3, effects.ColorFromCurrentPalette(hue, pow(fadeRate, step - 2) * 255)); + } + step++; + } + else { + step = -1; + } + } + + effects.standardNoiseSmearing(); + + effects.ShowFrame(); + + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternRadar.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternRadar.h new file mode 100644 index 0000000..602078b --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternRadar.h @@ -0,0 +1,56 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternRadar_H + +class PatternRadar : public Drawable { + private: + byte theta = 0; + byte hueoffset = 0; + + public: + PatternRadar() { + name = (char *)"Radar"; + } + + unsigned int drawFrame() { + effects.DimAll(254); effects.ShowFrame(); + + for (int offset = 0; offset < MATRIX_CENTER_X; offset++) { + byte hue = 255 - (offset * 16 + hueoffset); + CRGB color = effects.ColorFromCurrentPalette(hue); + uint8_t x = mapcos8(theta, offset, (VPANEL_W - 1) - offset); + uint8_t y = mapsin8(theta, offset, (VPANEL_H - 1) - offset); + uint16_t xy = XY(x, y); + effects.leds[xy] = color; + + EVERY_N_MILLIS(25) { + theta += 2; + hueoffset += 1; + } + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSimplexNoise.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSimplexNoise.h new file mode 100644 index 0000000..3810ec0 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSimplexNoise.h @@ -0,0 +1,79 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/blob/master/examples/Noise/Noise.ino + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSimplexNoise_H +#define PatternSimplexNoise_H + +class PatternSimplexNoise : public Drawable { + public: + PatternSimplexNoise() { + name = (char *)"Noise"; + } + + void start() { + // Initialize our coordinates to some random values + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + } + + unsigned int drawFrame() { +#if FASTLED_VERSION >= 3001000 + // a new parameter set every 15 seconds + EVERY_N_SECONDS(15) { + noise_x = random16(); + noise_y = random16(); + noise_z = random16(); + } +#endif + + uint32_t speed = 100; + + effects.FillNoise(); + ShowNoiseLayer(0, 1, 0); + + // noise_x += speed; + noise_y += speed; + noise_z += speed; + + effects.ShowFrame(); + + return 30; + } + + // show just one layer + void ShowNoiseLayer(byte layer, byte colorrepeat, byte colorshift) { + for (uint16_t i = 0; i < VPANEL_W; i++) { + for (uint16_t j = 0; j < VPANEL_H; j++) { + uint8_t pixel = noise[i][j]; + + // assign a color depending on the actual palette + effects.leds[XY16(i, j)] = effects.ColorFromCurrentPalette(colorrepeat * (pixel + colorshift), pixel); + } + } + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSnake.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSnake.h new file mode 100644 index 0000000..00895ab --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSnake.h @@ -0,0 +1,145 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from LedEffects Snake by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Snake.cpp?at=default + * Copyright (c) 2013 Robert Atkins + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSnake_H +#define PatternSnake_H + +class PatternSnake : public Drawable { +private: + static const byte SNAKE_LENGTH = 16; + + CRGB colors[SNAKE_LENGTH]; + uint8_t initialHue; + + enum Direction { + UP, DOWN, LEFT, RIGHT + }; + + struct Pixel { + uint8_t x; + uint8_t y; + }; + + struct Snake { + Pixel pixels[SNAKE_LENGTH]; + + Direction direction; + + void newDirection() { + switch (direction) { + case UP: + case DOWN: + direction = random(0, 2) == 1 ? RIGHT : LEFT; + break; + + case LEFT: + case RIGHT: + direction = random(0, 2) == 1 ? DOWN : UP; + + default: + break; + } + } + + void shuffleDown() { + for (byte i = SNAKE_LENGTH - 1; i > 0; i--) { + pixels[i] = pixels[i - 1]; + } + } + + void reset() { + direction = UP; + for (int i = 0; i < SNAKE_LENGTH; i++) { + pixels[i].x = 0; + pixels[i].y = 0; + } + } + + void move() { + switch (direction) { + case UP: + pixels[0].y = (pixels[0].y + 1) % VPANEL_H; + break; + case LEFT: + pixels[0].x = (pixels[0].x + 1) % VPANEL_W; + break; + case DOWN: + pixels[0].y = pixels[0].y == 0 ? VPANEL_H - 1 : pixels[0].y - 1; + break; + case RIGHT: + pixels[0].x = pixels[0].x == 0 ? VPANEL_W - 1 : pixels[0].x - 1; + break; + } + } + + void draw(CRGB colors[SNAKE_LENGTH]) { + for (byte i = 0; i < SNAKE_LENGTH; i++) { + effects.leds[XY(pixels[i].x, pixels[i].y)] = colors[i] %= (255 - i * (255 / SNAKE_LENGTH)); + } + } + }; + + static const int snakeCount = 6; + Snake snakes[snakeCount]; + +public: + PatternSnake() { + name = (char *)"Snake"; + for (int i = 0; i < snakeCount; i++) { + Snake* snake = &snakes[i]; + snake->reset(); + } + } + + void start() + { + effects.ClearFrame(); + } + + unsigned int drawFrame() { + + + fill_palette(colors, SNAKE_LENGTH, initialHue++, 5, effects.currentPalette, 255, LINEARBLEND); + + for (int i = 0; i < snakeCount; i++) { + Snake* snake = &snakes[i]; + + snake->shuffleDown(); + + if (random(10) > 7) { + snake->newDirection(); + } + + snake->move(); + snake->draw(colors); + } + + effects.ShowFrame(); + + return 30; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpark.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpark.h new file mode 100644 index 0000000..059d97c --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpark.h @@ -0,0 +1,113 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/tree/master/examples/Fire2012WithPalette + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSpark_H +#define PatternSpark_H + +class PatternSpark : public Drawable { + private: + + public: + PatternSpark() { + name = (char *)"Spark"; + } + + // There are two main parameters you can play with to control the look and + // feel of your fire: COOLING (used in step 1 above), and SPARKING (used + // in step 3 above). + // + // COOLING: How much does the air cool as it rises? + // Less cooling = taller flames. More cooling = shorter flames. + // Default 55, suggested range 20-100 + uint8_t cooling = 100; + + // SPARKING: What chance (out of 255) is there that a new spark will be lit? + // Higher chance = more roaring fire. Lower chance = more flickery fire. + // Default 120, suggested range 50-200. + uint8_t sparking = 50; + + unsigned int drawFrame() { + // Add entropy to random number generator; we use a lot of it. + random16_add_entropy( random16()); + + effects.DimAll(235); effects.ShowFrame(); + + for (uint8_t x = 0; x < VPANEL_W; x++) { + // Step 1. Cool down every cell a little + for (int y = 0; y < VPANEL_H; y++) { + int xy = XY(x, y); + heat[xy] = qsub8(heat[xy], random8(0, ((cooling * 10) / VPANEL_H) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int y = 0; y < VPANEL_H; y++) { + heat[XY(x, y)] = (heat[XY(x, y + 1)] + heat[XY(x, y + 2)] + heat[XY(x, y + 2)]) / 3; + } + + // Step 2. Randomly ignite new 'sparks' of heat + if (random8() < sparking) { + uint8_t xt = random8(MATRIX_CENTRE_X - 2, MATRIX_CENTER_X + 3); + + int xy = XY(xt, VPANEL_H - 1); + heat[xy] = qadd8(heat[xy], random8(160, 255)); + } + + // Step 4. Map from heat cells to LED colors + for (int y = 0; y < VPANEL_H; y++) { + int xy = XY(x, y); + byte colorIndex = heat[xy]; + + // Recommend that you use values 0-240 rather than + // the usual 0-255, as the last 15 colors will be + // 'wrapping around' from the hot end to the cold end, + // which looks wrong. + colorIndex = scale8(colorIndex, 240); + + // override color 0 to ensure a black background? + if (colorIndex != 0) + // effects.leds[xy] = CRGB::Black; + // else + effects.leds[xy] = effects.ColorFromCurrentPalette(colorIndex); + } + } + + // Noise + noise_x += 1000; + noise_y += 1000; + noise_z += 1000; + noise_scale_x = 4000; + noise_scale_y = 4000; + effects.FillNoise(); + + effects.MoveX(3); + effects.MoveFractionalNoiseX(4); + + effects.ShowFrame(); + + return 15; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpin.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpin.h new file mode 100644 index 0000000..c3497e7 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpin.h @@ -0,0 +1,100 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternSpin_H + + + +class PatternSpin : public Drawable { +public: + PatternSpin() { + name = (char *)"Spin"; + } + + float degrees = 0; + float radius = 16; + + float speedStart = 1; + float velocityStart = 0.6; + + float maxSpeed = 30; + + float speed = speedStart; + float velocity = velocityStart; + + void start() { + speed = speedStart; + velocity = velocityStart; + degrees = 0; + } + + unsigned int drawFrame() { + effects.DimAll(190); effects.ShowFrame(); + + CRGB color = effects.ColorFromCurrentPalette(speed * 8); + + // start position + int x; + int y; + + // target position + float targetDegrees = degrees + speed; + float targetRadians = radians(targetDegrees); + int targetX = (int) (MATRIX_CENTER_X + radius * cos(targetRadians)); + int targetY = (int) (MATRIX_CENTER_Y - radius * sin(targetRadians)); + + float tempDegrees = degrees; + + do{ + float radians = radians(tempDegrees); + x = (int) (MATRIX_CENTER_X + radius * cos(radians)); + y = (int) (MATRIX_CENTER_Y - radius * sin(radians)); + + effects.drawBackgroundFastLEDPixelCRGB(x, y, color); + effects.drawBackgroundFastLEDPixelCRGB(y, x, color); + + tempDegrees += 1; + if (tempDegrees >= 360) + tempDegrees = 0; + } while (x != targetX || y != targetY); + + degrees += speed; + + // add velocity to the particle each pass around the accelerator + if (degrees >= 360) { + degrees = 0; + speed += velocity; + if (speed <= speedStart) { + speed = speedStart; + velocity *= -1; + } + else if (speed > maxSpeed){ + speed = maxSpeed - velocity; + velocity *= -1; + } + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpiral.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpiral.h new file mode 100644 index 0000000..64ef15d --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpiral.h @@ -0,0 +1,138 @@ +/* + * Portions of this code are adapted from "Funky Clouds" by Stefan Petrick: + * https://gist.github.com/anonymous/876f908333cd95315c35 + * + * Copyright (c) 2014 Stefan Petrick + * http://www.stefan-petrick.de/wordpress_beta + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSpiral_H +#define PatternSpiral_H + +class PatternSpiral : public Drawable { +private: + // Timer stuff (Oscillators) + struct timer { + unsigned long takt; + unsigned long lastMillis; + unsigned long count; + int delta; + byte up; + byte down; + }; + timer multiTimer[5]; + + int timers = sizeof(multiTimer) / sizeof(multiTimer[0]); + + // counts all variables with different speeds linear up and down + void UpdateTimers() + { + unsigned long now = millis(); + for (int i = 0; i < timers; i++) + { + while (now - multiTimer[i].lastMillis >= multiTimer[i].takt) + { + multiTimer[i].lastMillis += multiTimer[i].takt; + multiTimer[i].count = multiTimer[i].count + multiTimer[i].delta; + if ((multiTimer[i].count == multiTimer[i].up) || (multiTimer[i].count == multiTimer[i].down)) + { + multiTimer[i].delta = -multiTimer[i].delta; + } + } + } + } + +public: + PatternSpiral() { + name = (char *)"Spiral"; + } + + void start() { + // set all counting directions positive for the beginning + for (int i = 0; i < timers; i++) multiTimer[i].delta = 1; + + // set range (up/down), speed (takt=ms between steps) and starting point of all oscillators + + unsigned long now = millis(); + + multiTimer[0].lastMillis = now; + multiTimer[0].takt = 42; //x1 + multiTimer[0].up = VPANEL_W - 1; + multiTimer[0].down = 0; + multiTimer[0].count = 0; + + multiTimer[1].lastMillis = now; + multiTimer[1].takt = 55; //y1 + multiTimer[1].up = VPANEL_H - 1; + multiTimer[1].down = 0; + multiTimer[1].count = 0; + + multiTimer[2].lastMillis = now; + multiTimer[2].takt = 3; //color + multiTimer[2].up = 255; + multiTimer[2].down = 0; + multiTimer[2].count = 0; + + multiTimer[3].lastMillis = now; + multiTimer[3].takt = 71; //x2 + multiTimer[3].up = VPANEL_W - 1; + multiTimer[3].down = 0; + multiTimer[3].count = 0; + + multiTimer[4].lastMillis = now; + multiTimer[4].takt = 89; //y2 + multiTimer[4].up = VPANEL_H - 1; + multiTimer[4].down = 0; + multiTimer[4].count = 0; + } + + unsigned int drawFrame() { + // manage the Oscillators + UpdateTimers(); + + // draw just a line defined by 5 oscillators + effects.BresenhamLine( + multiTimer[3].count, // x1 + multiTimer[4].count, // y1 + multiTimer[0].count, // x2 + multiTimer[1].count, // y2 + multiTimer[2].count); // color + + // manipulate the screen buffer + // with fixed parameters (could be oscillators too) + // Params: center x, y, radius, scale color down + // --> NOTE: Affects always a SQUARE with an odd length + // effects.SpiralStream(15, 15, 10, 128); + + effects.SpiralStream(31, 15, 64, 128); // for 64 pixel wide matrix! + // effects.SpiralStream(47, 15, 10, 128); // for 64 pixel wide matrix! + + // why not several times?! + // effects.SpiralStream(16, 6, 6, 128); + // effects.SpiralStream(10, 24, 10, 128); + + // increase the contrast + effects.DimAll(250); effects.ShowFrame(); + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpiro.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpiro.h new file mode 100644 index 0000000..4b91e12 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSpiro.h @@ -0,0 +1,112 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternSpiro_H + +class PatternSpiro : public Drawable { + private: + byte theta1 = 0; + byte theta2 = 0; + byte hueoffset = 0; + + uint8_t radiusx = VPANEL_W / 4; + uint8_t radiusy = VPANEL_H / 4; + uint8_t minx = MATRIX_CENTER_X - radiusx; + uint8_t maxx = MATRIX_CENTER_X + radiusx + 1; + uint8_t miny = MATRIX_CENTER_Y - radiusy; + uint8_t maxy = MATRIX_CENTER_Y + radiusy + 1; + + uint8_t spirocount = 1; + uint8_t spirooffset = 256 / spirocount; + boolean spiroincrement = true; + + boolean handledChange = false; + + public: + PatternSpiro() { + name = (char *)"Spiro"; + } + + void start(){ + effects.ClearFrame(); + }; + + unsigned int drawFrame() { + blur2d(effects.leds, VPANEL_W > 255 ? 255 : VPANEL_W, VPANEL_H > 255 ? 255 : VPANEL_H, 192); + + boolean change = false; + + for (int i = 0; i < spirocount; i++) { + uint8_t x = mapsin8(theta1 + i * spirooffset, minx, maxx); + uint8_t y = mapcos8(theta1 + i * spirooffset, miny, maxy); + + uint8_t x2 = mapsin8(theta2 + i * spirooffset, x - radiusx, x + radiusx); + uint8_t y2 = mapcos8(theta2 + i * spirooffset, y - radiusy, y + radiusy); + + CRGB color = effects.ColorFromCurrentPalette(hueoffset + i * spirooffset, 128); + effects.leds[XY(x2, y2)] += color; + + if((x2 == MATRIX_CENTER_X && y2 == MATRIX_CENTER_Y) || + (x2 == MATRIX_CENTRE_X && y2 == MATRIX_CENTRE_Y)) change = true; + } + + theta2 += 1; + + EVERY_N_MILLIS(25) { + theta1 += 1; + } + + EVERY_N_MILLIS(100) { + if (change && !handledChange) { + handledChange = true; + + if (spirocount >= VPANEL_W || spirocount == 1) spiroincrement = !spiroincrement; + + if (spiroincrement) { + if(spirocount >= 4) + spirocount *= 2; + else + spirocount += 1; + } + else { + if(spirocount > 4) + spirocount /= 2; + else + spirocount -= 1; + } + + spirooffset = 256 / spirocount; + } + + if(!change) handledChange = false; + } + + EVERY_N_MILLIS(33) { + hueoffset += 1; + } + + effects.ShowFrame(); + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSwirl.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSwirl.h new file mode 100644 index 0000000..49f3273 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternSwirl.h @@ -0,0 +1,79 @@ +/* +* Aurora: https://github.com/pixelmatix/aurora +* Copyright (c) 2014 Jason Coon +* +* Portions of this code are adapted from SmartMatrixSwirl by Mark Kriegsman: https://gist.github.com/kriegsman/5adca44e14ad025e6d3b +* https://www.youtube.com/watch?v=bsGBT-50cts +* Copyright (c) 2014 Mark Kriegsman +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +* the Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef PatternSwirl_H + +class PatternSwirl : public Drawable { + private: + const uint8_t borderWidth = 2; + + public: + PatternSwirl() { + name = (char *)"Swirl"; + } + + void start() { + effects.ClearFrame(); + } + + unsigned int drawFrame() { + // Apply some blurring to whatever's already on the matrix + // Note that we never actually clear the matrix, we just constantly + // blur it repeatedly. Since the blurring is 'lossy', there's + // an automatic trend toward black -- by design. + uint8_t blurAmount = beatsin8(2, 10, 255); + +#if FASTLED_VERSION >= 3001000 + blur2d(effects.leds, VPANEL_W > 255 ? 255 : VPANEL_W, VPANEL_H > 255 ? 255 : VPANEL_H, blurAmount); +#else + effects.DimAll(blurAmount); +#endif + + // Use two out-of-sync sine waves + uint8_t i = beatsin8(256/VPANEL_H, borderWidth, VPANEL_W - borderWidth); + uint8_t j = beatsin8(2048/VPANEL_W, borderWidth, VPANEL_H - borderWidth); + + // Also calculate some reflections + uint8_t ni = (VPANEL_W - 1) - i; + uint8_t nj = (VPANEL_H - 1) - j; + + // The color of each point shifts over time, each at a different speed. + uint16_t ms = millis(); + effects.leds[XY(i, j)] += effects.ColorFromCurrentPalette(ms / 11); + //effects.leds[XY(j, i)] += effects.ColorFromCurrentPalette(ms / 13); // this doesn't work for non-square matrices + effects.leds[XY(ni, nj)] += effects.ColorFromCurrentPalette(ms / 17); + //effects.leds[XY(nj, ni)] += effects.ColorFromCurrentPalette(ms / 29); // this doesn't work for non-square matrices + effects.leds[XY(i, nj)] += effects.ColorFromCurrentPalette(ms / 37); + effects.leds[XY(ni, j)] += effects.ColorFromCurrentPalette(ms / 41); + + + effects.ShowFrame(); + return 0; + + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternTest.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternTest.h new file mode 100644 index 0000000..7a4a07d --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternTest.h @@ -0,0 +1,20 @@ + +#ifndef PatternTest_H +#define PatternTest_H + +class PatternTest : public Drawable { + private: + + public: + PatternTest() { + name = (char *)"Test Pattern"; + } + + unsigned int drawFrame() { + + matrix->fillScreen(matrix->color565(128, 0, 0)); + return 1000; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternWave.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternWave.h new file mode 100644 index 0000000..90e7448 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/PatternWave.h @@ -0,0 +1,120 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PatternWave_H +#define PatternWave_H + +class PatternWave : public Drawable { +private: + byte thetaUpdate = 0; + byte thetaUpdateFrequency = 0; + byte theta = 0; + + byte hueUpdate = 0; + byte hueUpdateFrequency = 0; + byte hue = 0; + + byte rotation = 0; + + uint8_t scale = 256 / VPANEL_W; + + uint8_t maxX = VPANEL_W - 1; + uint8_t maxY = VPANEL_H - 1; + + uint8_t waveCount = 1; + +public: + PatternWave() { + name = (char *)"Wave"; + } + + void start() { + rotation = random(0, 4); + waveCount = random(1, 3); + + } + + unsigned int drawFrame() { + int n = 0; + + switch (rotation) { + case 0: + for (int x = 0; x < VPANEL_W; x++) { + n = quadwave8(x * 2 + theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(x, n, effects.ColorFromCurrentPalette(x + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(x, maxY - n, effects.ColorFromCurrentPalette(x + hue)); + } + break; + + case 1: + for (int y = 0; y < VPANEL_H; y++) { + n = quadwave8(y * 2 + theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(n, y, effects.ColorFromCurrentPalette(y + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(maxX - n, y, effects.ColorFromCurrentPalette(y + hue)); + } + break; + + case 2: + for (int x = 0; x < VPANEL_W; x++) { + n = quadwave8(x * 2 - theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(x, n, effects.ColorFromCurrentPalette(x + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(x, maxY - n, effects.ColorFromCurrentPalette(x + hue)); + } + break; + + case 3: + for (int y = 0; y < VPANEL_H; y++) { + n = quadwave8(y * 2 - theta) / scale; + effects.drawBackgroundFastLEDPixelCRGB(n, y, effects.ColorFromCurrentPalette(y + hue)); + if (waveCount == 2) + effects.drawBackgroundFastLEDPixelCRGB(maxX - n, y, effects.ColorFromCurrentPalette(y + hue)); + } + break; + } + + effects.DimAll(254); + effects.ShowFrame(); + + if (thetaUpdate >= thetaUpdateFrequency) { + thetaUpdate = 0; + theta++; + } + else { + thetaUpdate++; + } + + if (hueUpdate >= hueUpdateFrequency) { + hueUpdate = 0; + hue++; + } + else { + hueUpdate++; + } + + return 0; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Patterns.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Patterns.h new file mode 100644 index 0000000..398d57b --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Patterns.h @@ -0,0 +1,297 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Patterns_H +#define Patterns_H + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +#include "Vector.h" +#include "Boid.h" +#include "Attractor.h" + +/* + * Note from mrfaptastic: + * + * Commented out patterns are due to the fact they either didn't work properly with a non-square display, + * or from my personal opinion, are crap. + */ + +#include "PatternTest.h" +//#include "PatternNoiseSmearing.h" // Doesn't seem to work, omitting. +#include "PatternSpiro.h" +#include "PatternRadar.h" +#include "PatternSwirl.h" +#include "PatternPendulumWave.h" +#include "PatternFlowField.h" +#include "PatternIncrementalDrift.h" +#include "PatternIncrementalDrift2.h" // Doesn't seem to work, omitting. +#include "PatternMunch.h" +#include "PatternElectricMandala.h" +//#include "PatternSpin.h" // Doesn't seem to work, omitting. +#include "PatternSimplexNoise.h" +#include "PatternWave.h" +#include "PatternAttract.h" +//#include "PatternBounce.h" // Doesn't seem to work, omitting. +#include "PatternFlock.h" +#include "PatternInfinity.h" +#include "PatternPlasma.h" +#include "PatternSnake.h" +#include "PatternInvaders.h" +//#include "PatternCube.h" // Doesn't seem to work, omitting. +//#include "PatternFire.h" // Doesn't seem to work, omitting. +#include "PatternLife.h" +#include "PatternMaze.h" +//#include "PatternPulse.h" // Doesn't seem to work, omitting. +//#include "PatternSpark.h" // Doesn't seem to work, omitting. +#include "PatternSpiral.h" + +class Patterns : public Playlist { + private: + PatternTest patternTest; + // PatternRainbowFlag rainbowFlag; // doesn't work + // PatternPaletteSmear paletteSmear; + // PatternMultipleStream multipleStream; // doesn't work + // PatternMultipleStream2 multipleStream2; // doesn't work + // PatternMultipleStream3 multipleStream3; // doesn't work + // PatternMultipleStream4 multipleStream4; // doesn't work + // PatternMultipleStream5 multipleStream5; // doesn't work + // PatternMultipleStream8 multipleStream8; // doesn't work + PatternSpiro spiro; + // PatternRadar radar; + PatternSwirl swirl; + PatternPendulumWave pendulumWave; + PatternFlowField flowField; + PatternIncrementalDrift incrementalDrift; + PatternIncrementalDrift2 incrementalDrift2; + PatternMunch munch; + PatternElectricMandala electricMandala; + // PatternSpin spin; + PatternSimplexNoise simplexNoise; + PatternWave wave; + PatternAttract attract; + // PatternBounce bounce; + PatternFlock flock; + PatternInfinity infinity; + PatternPlasma plasma; + PatternInvadersSmall invadersSmall; + // PatternInvadersMedium invadersMedium; + // PatternInvadersLarge invadersLarge; + PatternSnake snake; + // PatternCube cube; + // PatternFire fire; + PatternLife life; + PatternMaze maze; + // PatternPulse pulse; + // PatternSpark spark; + PatternSpiral spiral; + + int currentIndex = 0; + Drawable* currentItem; + + int getCurrentIndex() { + return currentIndex; + } + + const static int PATTERN_COUNT = 14; + + Drawable* shuffledItems[PATTERN_COUNT]; + + Drawable* items[PATTERN_COUNT] = { + // &patternTest, // ok + &spiro, // cool + // &paletteSmear, // fail + // &multipleStream, // fail + // &multipleStream8,// fail + // &multipleStream5,// fail + // &multipleStream3,// fail + // &radar, // fail + // &multipleStream4, // fail + // &multipleStream2, // fail + &life, // ok + &flowField, + &pendulumWave, //11 ok + + &incrementalDrift, //12 ok + &incrementalDrift2, // 13 fail + &munch, // 14 ok + // &electricMandala, // 15 ok, but ugly (vortigont) + // &spin, // 16 ok but repetitive + // &simplexNoise, // 17 - cool! + // &wave, // 18 ok (can't work with 256+ matrix due to uint8_t vars) + // &rainbowFlag, //20 // fail + &attract, // 21 ok + // &swirl, // 22 ok, but ugly (vortigont) + // &bounce, // bouncing line crap + &flock, // works + &infinity, // works + &plasma, // works + // &invadersSmall, // works ish, but ugly (vortigont) + // &invadersMedium, // fail + // &invadersLarge, // fail + &snake, // ok + // &cube, // works ish + // &fire, // ok ish + &maze, // ok + // &pulse,// fail + // &spark, // same as fire + &spiral, // ok + }; + + public: + Patterns() { + // add the items to the shuffledItems array + for (int a = 0; a < PATTERN_COUNT; a++) { + shuffledItems[a] = items[a]; + } + + shuffleItems(); + + this->currentItem = items[0]; + this->currentItem->start(); + } + + char* Drawable::name = (char *)"Patterns"; + + void stop() { + if (currentItem) + currentItem->stop(); + } + + void start() { + if (currentItem) + currentItem->start(); + } + + void move(int step) { + currentIndex += step; + + if (currentIndex >= PATTERN_COUNT) currentIndex = 0; + else if (currentIndex < 0) currentIndex = PATTERN_COUNT - 1; + + if (effects.paletteIndex == effects.RandomPaletteIndex) + effects.RandomPalette(); + + moveTo(currentIndex); + + //if (!isTimeAvailable && currentItem == &analogClock) + // move(step); + } + + void moveRandom(int step) { + currentIndex += step; + + if (currentIndex >= PATTERN_COUNT) currentIndex = 0; + else if (currentIndex < 0) currentIndex = PATTERN_COUNT - 1; + + if (effects.paletteIndex == effects.RandomPaletteIndex) + effects.RandomPalette(); + + if (currentItem) + currentItem->stop(); + + currentItem = shuffledItems[currentIndex]; + + if (currentItem) + currentItem->start(); + + // if (!isTimeAvailable && currentItem == &analogClock) + // moveRandom(step); + } + + void shuffleItems() { + for (int a = 0; a < PATTERN_COUNT; a++) + { + int r = random(a, PATTERN_COUNT); + Drawable* temp = shuffledItems[a]; + shuffledItems[a] = shuffledItems[r]; + shuffledItems[r] = temp; + } + } + + + unsigned int drawFrame() { + return currentItem->drawFrame(); + } + + void listPatterns() { + Serial.println(F("{")); + Serial.print(F(" \"count\": ")); + Serial.print(PATTERN_COUNT); + Serial.println(","); + Serial.println(F(" \"results\": [")); + + for (int i = 0; i < PATTERN_COUNT; i++) { + Serial.print(F(" \"")); + Serial.print(i, DEC); + Serial.print(F(": ")); + Serial.print(items[i]->name); + if (i == PATTERN_COUNT - 1) + Serial.println(F("\"")); + else + Serial.println(F("\",")); + } + + Serial.println(" ]"); + Serial.println("}"); + } + + char * getCurrentPatternName() + { + return currentItem->name; + } + + void moveTo(int index) { + if (currentItem) + currentItem->stop(); + + currentIndex = index; + + currentItem = items[currentIndex]; + + if (currentItem) + currentItem->start(); + } + + bool setPattern(String name) { + for (int i = 0; i < PATTERN_COUNT; i++) { + if (name.compareTo(items[i]->name) == 0) { + moveTo(i); + return true; + } + } + + return false; + } + + + bool setPattern(int index) { + if (index >= PATTERN_COUNT || index < 0) + return false; + + moveTo(index); + + return true; + } +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Playlist.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Playlist.h new file mode 100644 index 0000000..29c0c87 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Playlist.h @@ -0,0 +1,39 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Playlist_H +#define Playlist_H + +class Playlist : public Drawable { +public: + virtual bool isPlaylist() { + return true; + } + + boolean isCurrentItemFinished = true; + + virtual void move(int step) = 0; + virtual void moveRandom(int step) = 0; + virtual int getCurrentIndex(); +}; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Vector.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Vector.h new file mode 100644 index 0000000..8acbadc --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/ChainedPanelsAuroraDemo/Vector.h @@ -0,0 +1,169 @@ +/* + * Aurora: https://github.com/pixelmatix/aurora + * Copyright (c) 2014 Jason Coon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef Vector_H +#define Vector_H + +template +class Vector2 { +public: + T x, y; + + Vector2() :x(0), y(0) {} + Vector2(T x, T y) : x(x), y(y) {} + Vector2(const Vector2& v) : x(v.x), y(v.y) {} + + Vector2& operator=(const Vector2& v) { + x = v.x; + y = v.y; + return *this; + } + + bool isEmpty() { + return x == 0 && y == 0; + } + + bool operator==(Vector2& v) { + return x == v.x && y == v.y; + } + + bool operator!=(Vector2& v) { + return !(x == y); + } + + Vector2 operator+(Vector2& v) { + return Vector2(x + v.x, y + v.y); + } + Vector2 operator-(Vector2& v) { + return Vector2(x - v.x, y - v.y); + } + + Vector2& operator+=(Vector2& v) { + x += v.x; + y += v.y; + return *this; + } + Vector2& operator-=(Vector2& v) { + x -= v.x; + y -= v.y; + return *this; + } + + Vector2 operator+(double s) { + return Vector2(x + s, y + s); + } + Vector2 operator-(double s) { + return Vector2(x - s, y - s); + } + Vector2 operator*(double s) { + return Vector2(x * s, y * s); + } + Vector2 operator/(double s) { + return Vector2(x / s, y / s); + } + + Vector2& operator+=(double s) { + x += s; + y += s; + return *this; + } + Vector2& operator-=(double s) { + x -= s; + y -= s; + return *this; + } + Vector2& operator*=(double s) { + x *= s; + y *= s; + return *this; + } + Vector2& operator/=(double s) { + x /= s; + y /= s; + return *this; + } + + void set(T x, T y) { + this->x = x; + this->y = y; + } + + void rotate(double deg) { + double theta = deg / 180.0 * M_PI; + double c = cos(theta); + double s = sin(theta); + double tx = x * c - y * s; + double ty = x * s + y * c; + x = tx; + y = ty; + } + + Vector2& normalize() { + if (length() == 0) return *this; + *this *= (1.0 / length()); + return *this; + } + + float dist(Vector2 v) const { + Vector2 d(v.x - x, v.y - y); + return d.length(); + } + float length() const { + return sqrt(x * x + y * y); + } + + float mag() const { + return length(); + } + + float magSq() { + return (x * x + y * y); + } + + void truncate(double length) { + double angle = atan2f(y, x); + x = length * cos(angle); + y = length * sin(angle); + } + + Vector2 ortho() const { + return Vector2(y, -x); + } + + static float dot(Vector2 v1, Vector2 v2) { + return v1.x * v2.x + v1.y * v2.y; + } + static float cross(Vector2 v1, Vector2 v2) { + return (v1.x * v2.y) - (v1.y * v2.x); + } + + void limit(float max) { + if (magSq() > max*max) { + normalize(); + *this *= max; + } + } +}; + +typedef Vector2 PVector; + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Four_Scan_Panel/Four_Scan_Panel.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Four_Scan_Panel/Four_Scan_Panel.ino new file mode 100644 index 0000000..5086b62 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Four_Scan_Panel/Four_Scan_Panel.ino @@ -0,0 +1,153 @@ +/************************************************************************* + * Description: + * + * The underlying implementation of the ESP32-HUB75-MatrixPanel-I2S-DMA only + * supports output to HALF scan panels - which means outputting + * two lines at the same time, 16 or 32 rows apart if a 32px or 64px high panel + * respectively. + * This cannot be changed at the DMA layer as it would require a messy and complex + * rebuild of the library's internals. + * + * However, it is possible to connect QUARTER (i.e. FOUR lines updated in parallel) + * scan panels to this same library and + * 'trick' the output to work correctly on these panels by way of adjusting the + * pixel co-ordinates that are 'sent' to the ESP32-HUB75-MatrixPanel-I2S-DMA + * library. + * + **************************************************************************/ +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" + +/* Use the Virtual Display class to re-map co-ordinates such that they draw + * correctly on a 32x16 1/8 Scan panel (or chain of such panels). + */ +#include "ESP32-VirtualMatrixPanel-I2S-DMA.h" + + + // Panel configuration + #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. + #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. + + + #define NUM_ROWS 1 // Number of rows of chained INDIVIDUAL PANELS + #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + + // ^^^ NOTE: DEFAULT EXAMPLE SETUP IS FOR A CHAIN OF TWO x 1/8 SCAN PANELS + + // Change this to your needs, for details on VirtualPanel pls read the PDF! + #define SERPENT true + #define TOPDOWN false + + // placeholder for the matrix object + MatrixPanel_I2S_DMA *dma_display = nullptr; + + // placeholder for the virtual display object + VirtualMatrixPanel *FourScanPanel = nullptr; + + /****************************************************************************** + * Setup! + ******************************************************************************/ + void setup() + { + delay(250); + + Serial.begin(115200); + Serial.println(""); Serial.println(""); Serial.println(""); + Serial.println("*****************************************************"); + Serial.println("* 1/8 Scan Panel Demonstration *"); + Serial.println("*****************************************************"); + +/* + // 62x32 1/8 Scan Panels don't have a D and E pin! + + HUB75_I2S_CFG::i2s_pins _pins = { + R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, + A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, + LAT_PIN, OE_PIN, CLK_PIN + }; +*/ + HUB75_I2S_CFG mxconfig( + PANEL_RES_X*2, // DO NOT CHANGE THIS + PANEL_RES_Y/2, // DO NOT CHANGE THIS + NUM_ROWS*NUM_COLS // DO NOT CHANGE THIS + //,_pins // Uncomment to enable custom pins + ); + + mxconfig.clkphase = false; // Change this if you see pixels showing up shifted wrongly by one column the left or right. + + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object + + // OK, now we can create our matrix object + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // let's adjust default brightness to about 75% + dma_display->setBrightness8(96); // range is 0-255, 0 - 0%, 255 - 100% + + // Allocate memory and start DMA display + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! I2S memory allocation failed ***********"); + + + dma_display->clearScreen(); + delay(500); + + // create FourScanPanellay object based on our newly created dma_display object + FourScanPanel = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y); + + // THE IMPORTANT BIT BELOW! + FourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); + } + + + void loop() { + + // What the panel sees from the DMA engine! + for (int i=PANEL_RES_X*2+10; i< PANEL_RES_X*(NUM_ROWS*NUM_COLS)*2; i++) + { + dma_display->drawLine(i, 0, i, 7, dma_display->color565(255, 0, 0)); // red + delay(10); + } + + dma_display->clearScreen(); + delay(1000); +/* + // Try again using the pixel / dma memory remapper + for (int i=PANEL_RES_X+5; i< (PANEL_RES_X*2)-1; i++) + { + FourScanPanel->drawLine(i, 0, i, 7, dma_display->color565(0, 0, 255)); // blue + delay(10); + } +*/ + + // Try again using the pixel / dma memory remapper + int offset = PANEL_RES_X*((NUM_ROWS*NUM_COLS)-1); + for (int i=0; i< PANEL_RES_X; i++) + { + FourScanPanel->drawLine(i+offset, 0, i+offset, 7, dma_display->color565(0, 0, 255)); // blue + FourScanPanel->drawLine(i+offset, 8, i+offset, 15, dma_display->color565(0, 128,0)); // g + FourScanPanel->drawLine(i+offset, 16, i+offset, 23, dma_display->color565(128, 0,0)); // red + FourScanPanel->drawLine(i+offset, 24, i+offset, 31, dma_display->color565(0, 128, 128)); // blue + delay(10); + } + + delay(1000); + + + // Print on each chained panel 1/8 module! + // This only really works for a single horizontal chain + for (int i = 0; i < NUM_ROWS*NUM_COLS; i++) + { + FourScanPanel->setTextColor(FourScanPanel->color565(255, 255, 255)); + FourScanPanel->setCursor(i*PANEL_RES_X+7, FourScanPanel->height()/3); + + // Red text inside red rect (2 pix in from edge) + FourScanPanel->print("Panel " + String(i+1)); + FourScanPanel->drawRect(1,1, FourScanPanel->width()-2, FourScanPanel->height()-2, FourScanPanel->color565(255,0,0)); + + // White line from top left to bottom right + FourScanPanel->drawLine(0,0, FourScanPanel->width()-1, FourScanPanel->height()-1, FourScanPanel->color565(255,255,255)); + } + + delay(2000); + dma_display->clearScreen(); + + } // end loop diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Four_Scan_Panel/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Four_Scan_Panel/README.md new file mode 100644 index 0000000..7df7617 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Four_Scan_Panel/README.md @@ -0,0 +1,7 @@ +# Using this library with 32x16 1/8 Scan Panels + +## Problem +ESP32-HUB75-MatrixPanel-I2S-DMA library will not display output correctly with 'Four Scan' or 1/8 scan panels such [as this](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154) by default. + +## Solution +It is possible to connect 1/8 scan panels to this library and 'trick' the output to work correctly on these panels by way of adjusting the pixel co-ordinates that are 'sent' to the underlying ESP32-HUB75-MatrixPanel-I2S-DMA library (in this example, it is the 'dmaOutput' class). diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/HueValueSpectrum/HueValueSpectrum.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/HueValueSpectrum/HueValueSpectrum.ino new file mode 100644 index 0000000..897275e --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/HueValueSpectrum/HueValueSpectrum.ino @@ -0,0 +1,80 @@ +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +#include + +MatrixPanel_I2S_DMA *dma_display = nullptr; + +void setup() { + + Serial.begin(112500); + + + HUB75_I2S_CFG::i2s_pins _pins={ + 25, //R1_PIN, + 26, //G1_PIN, + 27, //B1_PIN, + 14, //R2_PIN, + 12, //G2_PIN, + 13, //B2_PIN, + 23, //A_PIN, + 19, //B_PIN, + 5, //C_PIN, + 17, //D_PIN, + 18, //E_PIN, + 4, //LAT_PIN, + 15, //OE_PIN, + 16, //CLK_PIN + }; + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // Module width + PANEL_RES_Y, // Module height + PANEL_CHAIN //, // chain length + //_pins // pin mapping -- uncomment if providing own custom pin mapping as per above. + ); + //mxconfig.clkphase = false; + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // Display Setup + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->clearScreen(); +} + +void loop() { + // Canvas loop + float t = (float) ((millis()%4000)/4000.f); + float tt = (float) ((millis()%16000)/16000.f); + + for(int x = 0; x < (PANEL_RES_X*PANEL_CHAIN); x++) + { + // calculate the overal shade + float f = (((sin(tt-(float)x/PANEL_RES_Y/32.)*2.f*PI)+1)/2)*255; + // calculate hue spectrum into rgb + float r = max(min(cosf(2.f*PI*(t+((float)x/PANEL_RES_Y+0.f)/3.f))+0.5f,1.f),0.f); + float g = max(min(cosf(2.f*PI*(t+((float)x/PANEL_RES_Y+1.f)/3.f))+0.5f,1.f),0.f); + float b = max(min(cosf(2.f*PI*(t+((float)x/PANEL_RES_Y+2.f)/3.f))+0.5f,1.f),0.f); + + // iterate pixels for every row + for(int y = 0; y < PANEL_RES_Y; y++){ + if(y*2 < PANEL_RES_Y){ + // top-middle part of screen, transition of value + float t = (2.f*y+1)/PANEL_RES_Y; + dma_display->drawPixelRGB888(x,y, + (r*t)*f, + (g*t)*f, + (b*t)*f + ); + }else{ + // middle to bottom of screen, transition of saturation + float t = (2.f*(PANEL_RES_Y-y)-1)/PANEL_RES_Y; + dma_display->drawPixelRGB888(x,y, + (r*t+1-t)*f, + (g*t+1-t)*f, + (b*t+1-t)*f + ); + } + } + } +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Julia_Set_Demo/Julia_Set_Demo.ino b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Julia_Set_Demo/Julia_Set_Demo.ino new file mode 100644 index 0000000..9193006 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/Julia_Set_Demo/Julia_Set_Demo.ino @@ -0,0 +1,158 @@ +#define PANEL_RES_X 128 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 64 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another +#define USE_FLOATHACK // To boost float performance, comment if this doesn't work. + +#include + +MatrixPanel_I2S_DMA *dma_display = nullptr; + +// inspired by +// https://en.wikipedia.org/wiki/Fast_inverse_square_root +#ifdef USE_FLOATHACK +// cast float as int32_t + int32_t intfloat(float n){ return *(int32_t *)&n; } +// cast int32_t as float + float floatint(int32_t n){ return *(float *)&n; } +// fast approx sqrt(x) + float floatsqrt(float n){ return floatint(0x1fbb4000+(intfloat(n)>>1)); } +// fast approx 1/x + float floatinv(float n){ return floatint(0x7f000000-intfloat(n)); } +// fast approx log2(x) + float floatlog2(float n){ return (float)((intfloat(n)<<1)-0x7f000000)*5.9604645e-08f; } +#else + float floatinv(float n){ return 1.f/n;} + float floatsqrt(float n){ return std::sqrt(n); } + float floatlog2(float n){ return std::log2f(n); } +#endif + +//////////////////////////////////////// +// Escape time mandelbrot set function, +// with arbitrary start point zx, zy +// and arbitrary seed point ax, ay +// +// For julia set +// zx = pos_x, zy = pos_y; +// ax = seed_x, ay = seed_y; +// +// For mandelbrot set +// zx = 0, zy = 0; +// ax = pos_x, ay = pos_y; +// +const float bailOut = 4; // Escape radius +const int32_t itmult = 1<<10; // Color speed +// +// https://en.wikipedia.org/wiki/Mandelbrot_set +int32_t iteratefloat(float ax, float ay, float zx, float zy, uint16_t mxIT) { + float zzl = 0; + for (int it = 0; it=bailOut){ + if(it>0){ + // calculate smooth coloring + float zza = floatlog2(zzl); + float zzb = floatlog2(zzx+zzy); + float zzc = floatlog2(bailOut); + float zzd = (zzc-zza)*floatinv(zzb-zza); + return it*itmult+zzd*itmult; + } + }; + // z -> z*z + c + zy = 2.f*zx*zy+ay; + zx = zzx-zzy+ax; + zzl = zzx+zzy; + } + return 0; +} + +float sint[256]; // precalculated sin table, for performance reasons + +// Palette color taken from: +// https://editor.p5js.org/Kouzerumatsukite/sketches/DwTiq9D01 +// color palette originally made by piano_miles, written in p5js +// hsv2rgb(IT, cos(4096*it)/2+0.5, 1-sin(2048*it)/2-0.5) +void drawPixelPalette(int x, int y, uint32_t m){ + float r = 0.f, g = 0.f, b = 0.f; + if(m){ + char n = m>> 4 ; + float l =abs(sint[m>> 2&255] )*255.f ; + float s = (sint[m &255]+ 1.f)*0.5f ; + r = (max(min(sint[n &255]+0.5f,1.f),0.f)*s+(1-s))*l; + g = (max(min(sint[n+ 85&255]+0.5f,1.f),0.f)*s+(1-s))*l; + b = (max(min(sint[n+170&255]+0.5f,1.f),0.f)*s+(1-s))*l; + } + dma_display->drawPixelRGB888(x,y,r,g,b); +} + +void drawCanvas() { + uint32_t lastMicros = micros(); + double t = (double)lastMicros/8000000; + double k = sin(t*3.212/2)*sin(t*3.212/2)/16+1; + float cosk = (k-cos(t))/2; + float xoff = (cos(t)*cosk+k/2-0.25); + float yoff = (sin(t)*cosk ); + for(uint8_t y=0;ybegin(); + dma_display->clearScreen(); + dma_display->setBrightness(64); + setCpuFrequencyMhz(240); + + for(int i=0;i<256;i++){ + sint[i] = sinf(i/256.f*2.f*PI); + } + + xTaskCreatePinnedToCore(\ + Task1code, /* Function to implement the task */\ + "Task1", /* Name of the task */\ + 10000, /* Stack size in words */\ + NULL, /* Task input parameter */\ + 4, /* Priority of the task */\ + NULL, /* Task handle. */\ + 0); /* Core where the task should run */ + + Serial.begin(115200); +} + +uint64_t lastMillis=0; +void loop() { + if(millis()-lastMillis>=1000){ + // log frame rate to serial + Serial.print("fps: "); + Serial.println(frameCounts); + lastMillis += 1000; + frameCounts=0; + } +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/README.md new file mode 100644 index 0000000..ac988f6 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/README.md @@ -0,0 +1,18 @@ +# Test Patterns + +Simple solid colors, gradients and test line patterns, could be used to test matrices for proper operation, flickering and estimate fillrate timings. + +It is also used in CI test builds to check different build flags scenarios. + +Should be build and uploaded as a [platformio](https://platformio.org/) project + + +To build with Arduino's framework use +``` +pio run -t upload +``` + +To build using ESP32 IDF with arduino's component use +``` +pio run -t upload -e idfarduino +``` diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/platformio.ini b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/platformio.ini new file mode 100644 index 0000000..185513a --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/platformio.ini @@ -0,0 +1,23 @@ +[platformio] +default_envs = esp32 +description = HUB75 ESP32 I2S DMA test patterns example +;src_dir = src + +[env] +platform = espressif32 +board = wemos_d1_mini32 +lib_deps = + fastled/FastLED + Wire + adafruit/Adafruit BusIO + adafruit/Adafruit GFX Library + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA.git +upload_speed = 460800 +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder + +[env:esp32] +framework = arduino + +[env:esp32idf] +framework = arduino, espidf diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/sdkconfig.defaults b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/sdkconfig.defaults new file mode 100644 index 0000000..ee16cd3 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/sdkconfig.defaults @@ -0,0 +1,19 @@ +# Override some defaults to enable Arduino framework +CONFIG_ENABLE_ARDUINO_DEPENDS=y +CONFIG_AUTOSTART_ARDUINO=y +CONFIG_ARDUINO_RUN_CORE1=y +CONFIG_ARDUINO_RUNNING_CORE=1 +CONFIG_ARDUINO_EVENT_RUN_CORE1=y +CONFIG_ARDUINO_EVENT_RUNNING_CORE=1 +CONFIG_ARDUINO_UDP_RUN_CORE1=y +CONFIG_ARDUINO_UDP_RUNNING_CORE=1 +CONFIG_DISABLE_HAL_LOCKS=y +CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL_ERROR=y +CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL=1 +CONFIG_ARDUHAL_PARTITION_SCHEME_DEFAULT=y +CONFIG_ARDUHAL_PARTITION_SCHEME="default" +CONFIG_AUTOCONNECT_WIFI=y +CONFIG_ARDUINO_SELECTIVE_WiFi=y +CONFIG_MBEDTLS_PSK_MODES=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y +CONFIG_FREERTOS_HZ=1000 \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/CMakeLists.txt b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/CMakeLists.txt new file mode 100644 index 0000000..483bc0c --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/CMakeLists.txt @@ -0,0 +1,6 @@ +# This file was automatically generated for projects +# without default 'CMakeLists.txt' file. + +FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) + +idf_component_register(SRCS ${app_sources}) diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/main.cpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/main.cpp new file mode 100644 index 0000000..4e0edb1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/main.cpp @@ -0,0 +1,455 @@ +// How to use this library with a FM6126 panel, thanks goes to: +// https://github.com/hzeller/rpi-rgb-led-matrix/issues/746 + +#ifdef IDF_BUILD +#include +#include +#include +#include +#include "sdkconfig.h" +#endif + +#include +#include "xtensa/core-macros.h" +#ifdef VIRTUAL_PANE +#include +#else +#include +#endif +#include "main.h" + +// HUB75E pinout +// R1 | G1 +// B1 | GND +// R2 | G2 +// B2 | E +// A | B +// C | D +// CLK| LAT +// OE | GND + +/* Default library pin configuration for the reference + you can redefine only ones you need later on object creation +#define R1 25 +#define G1 26 +#define BL1 27 +#define R2 14 +#define G2 12 +#define BL2 13 +#define CH_A 23 +#define CH_B 19 +#define CH_C 5 +#define CH_D 17 +#define CH_E -1 // assign to any available pin if using panels with 1/32 scan +#define CLK 16 +#define LAT 4 +#define OE 15 +*/ + +// Configure for your panel(s) as appropriate! +#define PIN_E 32 +#define PANEL_WIDTH 64 +#define PANEL_HEIGHT 64 // Panel height of 64 will required PIN_E to be defined. + +#ifdef VIRTUAL_PANE + #define PANELS_NUMBER 4 // Number of chained panels, if just a single panel, obviously set to 1 +#else + #define PANELS_NUMBER 2 // Number of chained panels, if just a single panel, obviously set to 1 +#endif + +#define PANE_WIDTH PANEL_WIDTH * PANELS_NUMBER +#define PANE_HEIGHT PANEL_HEIGHT +#define NUM_LEDS PANE_WIDTH*PANE_HEIGHT + +#ifdef VIRTUAL_PANE + #define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS + #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + #define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another + // Change this to your needs, for details on VirtualPanel pls read the PDF! + #define SERPENT true + #define TOPDOWN false +#endif + + +#ifdef VIRTUAL_PANE +VirtualMatrixPanel *matrix = nullptr; +MatrixPanel_I2S_DMA *chain = nullptr; +#else +MatrixPanel_I2S_DMA *matrix = nullptr; +#endif +// patten change delay +#define PATTERN_DELAY 2000 + +uint16_t time_counter = 0, cycles = 0, fps = 0; +unsigned long fps_timer; + +// gradient buffer +CRGB *ledbuff; +// + +unsigned long t1, t2, s1=0, s2=0, s3=0; +uint32_t ccount1, ccount2; + +uint8_t color1 = 0, color2 = 0, color3 = 0; +uint16_t x,y; + +const char *str = "* ESP32 I2S DMA *"; + +void setup(){ + + Serial.begin(BAUD_RATE); + Serial.println("Starting pattern test..."); + + // redefine pins if required + //HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; + HUB75_I2S_CFG mxconfig(PANEL_WIDTH, PANEL_HEIGHT, PANELS_NUMBER); + + mxconfig.gpio.e = PIN_E; + mxconfig.driver = HUB75_I2S_CFG::FM6126A; // for panels using FM6126A chips + +#ifndef VIRTUAL_PANE + matrix = new MatrixPanel_I2S_DMA(mxconfig); + matrix->begin(); + matrix->setBrightness8(255); +#else + chain = new MatrixPanel_I2S_DMA(mxconfig); + chain->begin(); + chain->setBrightness8(255); + // create VirtualDisplay object based on our newly created dma_display object + matrix = new VirtualMatrixPanel((*chain), NUM_ROWS, NUM_COLS, PANEL_WIDTH, PANEL_HEIGHT, CHAIN_TOP_LEFT_DOWN); +#endif + + ledbuff = (CRGB *)malloc(NUM_LEDS * sizeof(CRGB)); // allocate buffer for some tests + buffclear(ledbuff); +} + +uint8_t wheelval = 0; +void loop(){ + + Serial.printf("Cycle: %d\n", ++cycles); + +#ifndef NO_GFX + drawText(wheelval++); +#endif + + Serial.print("Estimating clearScreen() - "); + ccount1 = XTHAL_GET_CCOUNT(); + matrix->clearScreen(); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + Serial.printf("%d ticks\n", ccount1); + delay(PATTERN_DELAY); + +/* +// Power supply tester +// slowly fills matrix with white, stressing PSU + for (int y=0; y!=PANE_HEIGHT; ++y){ + for (int x=0; x!=PANE_WIDTH; ++x){ + matrix->drawPixelRGB888(x, y, 255,255,255); + //matrix->drawPixelRGB888(x, y-1, 255,0,0); // pls, be gentle :) + delay(10); + } + } + delay(5000); +*/ + +#ifndef VIRTUAL_PANE + // simple solid colors + Serial.println("Fill screen: RED"); + matrix->fillScreenRGB888(255, 0, 0); + delay(PATTERN_DELAY); + Serial.println("Fill screen: GREEN"); + matrix->fillScreenRGB888(0, 255, 0); + delay(PATTERN_DELAY); + Serial.println("Fill screen: BLUE"); + matrix->fillScreenRGB888(0, 0, 255); + delay(PATTERN_DELAY); +#endif + + for (uint8_t i=5; i; --i){ + Serial.print("Estimating single drawPixelRGB888(r, g, b) ticks: "); + color1 = random8(); + ccount1 = XTHAL_GET_CCOUNT(); + matrix->drawPixelRGB888(i,i, color1, color1, color1); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + Serial.printf("%d ticks\n", ccount1); + } + +// Clearing CRGB ledbuff + Serial.print("Estimating ledbuff clear time: "); + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + buffclear(ledbuff); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n\n", t2, ccount1); + +#ifndef VIRTUAL_PANE + // Bare fillscreen(r, g, b) + Serial.print("Estimating fillscreenRGB888(r, g, b) time: "); + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + matrix->fillScreenRGB888(64, 64, 64); // white + ccount2 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + s1+=t2; + Serial.printf("%lu us, avg: %lu, ccnt: %d\n", t2, s1/cycles, ccount2); + delay(PATTERN_DELAY); +#endif + + Serial.print("Estimating full-screen fillrate with looped drawPixelRGB888(): "); + y = PANE_HEIGHT; + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + do { + --y; + uint16_t x = PANE_WIDTH; + do { + --x; + matrix->drawPixelRGB888( x, y, 0, 0, 0); + } while(x); + } while(y); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n", t2, ccount1); + + + +// created random color gradient in ledbuff + uint8_t color1 = 0; + uint8_t color2 = random8(); + uint8_t color3 = 0; + + for (uint16_t i = 0; ifillRect(0, 0, PANE_WIDTH, PANE_HEIGHT, 0, 224, 0); + t2 = micros()-t1; + Serial.printf("%lu us\n", t2); + delay(PATTERN_DELAY); + + + Serial.print("Chessboard with fillRect(): "); // шахматка + matrix->fillScreen(0); + x =0, y = 0; + color1 = random8(); + color2 = random8(); + color3 = random8(); + bool toggle=0; + t1 = micros(); + do { + do{ + matrix->fillRect(x, y, 8, 8, color1, color2, color3); + x+=16; + }while(x < PANE_WIDTH); + y+=8; + toggle = !toggle; + x = toggle ? 8 : 0; + }while(y < PANE_HEIGHT); + t2 = micros()-t1; + Serial.printf("%lu us\n", t2); + delay(PATTERN_DELAY); +#endif + +// ======== V-Lines ========== + Serial.println("Estimating V-lines with drawPixelRGB888(): "); // + matrix->fillScreen(0); + color1 = random8(); + color2 = random8(); + x = y = 0; + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + do { + y=0; + do{ + matrix->drawPixelRGB888(x, y, color1, color2, color3); + } while(++y != PANE_HEIGHT); + x+=2; + } while(x != PANE_WIDTH); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n", t2, ccount1); + delay(PATTERN_DELAY); + +#ifndef NO_FAST_FUNCTIONS + Serial.println("Estimating V-lines with vlineDMA(): "); // + matrix->fillScreen(0); + color2 = random8(); + x = y = 0; + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + do { + matrix->drawFastVLine(x, y, PANE_HEIGHT, color1, color2, color3); + x+=2; + } while(x != PANE_WIDTH); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n", t2, ccount1); + delay(PATTERN_DELAY); + + Serial.println("Estimating V-lines with fillRect(): "); // + matrix->fillScreen(0); + color1 = random8(); + color2 = random8(); + x = y = 0; + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + do { + matrix->fillRect(x, y, 1, PANE_HEIGHT, color1, color2, color3); + x+=2; + } while(x != PANE_WIDTH); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n", t2, ccount1); + delay(PATTERN_DELAY); +#endif + + + +// ======== H-Lines ========== + Serial.println("Estimating H-lines with drawPixelRGB888(): "); // + matrix->fillScreen(0); + color2 = random8(); + x = y = 0; + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + do { + x=0; + do{ + matrix->drawPixelRGB888(x, y, color1, color2, color3); + } while(++x != PANE_WIDTH); + y+=2; + } while(y != PANE_HEIGHT); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n", t2, ccount1); + delay(PATTERN_DELAY); + +#ifndef NO_FAST_FUNCTIONS + Serial.println("Estimating H-lines with hlineDMA(): "); + matrix->fillScreen(0); + color2 = random8(); + color3 = random8(); + x = y = 0; + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + do { + matrix->drawFastHLine(x, y, PANE_WIDTH, color1, color2, color3); + y+=2; + } while(y != PANE_HEIGHT); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n", t2, ccount1); + delay(PATTERN_DELAY); + + Serial.println("Estimating H-lines with fillRect(): "); // + matrix->fillScreen(0); + color2 = random8(); + color3 = random8(); + x = y = 0; + t1 = micros(); + ccount1 = XTHAL_GET_CCOUNT(); + do { + matrix->fillRect(x, y, PANE_WIDTH, 1, color1, color2, color3); + y+=2; + } while(y != PANE_HEIGHT); + ccount1 = XTHAL_GET_CCOUNT() - ccount1; + t2 = micros()-t1; + Serial.printf("%lu us, %u ticks\n", t2, ccount1); + delay(PATTERN_DELAY); +#endif + + + + + Serial.println("\n====\n"); + + // take a rest for a while + delay(10000); +} + + +void buffclear(CRGB *buf){ + memset(buf, 0x00, NUM_LEDS * sizeof(CRGB)); // flush buffer to black +} + +void IRAM_ATTR mxfill(CRGB *leds){ + uint16_t y = PANE_HEIGHT; + do { + --y; + uint16_t x = PANE_WIDTH; + do { + --x; + uint16_t _pixel = y * PANE_WIDTH + x; + matrix->drawPixelRGB888( x, y, leds[_pixel].r, leds[_pixel].g, leds[_pixel].b); + } while(x); + } while(y); +} +// + +/** + * The one for 256+ matrices + * otherwise this: + * for (uint8_t i = 0; i < MATRIX_WIDTH; i++) {} + * turns into an infinite loop + */ +uint16_t XY16( uint16_t x, uint16_t y) +{ + if (xsetTextSize(1); // size 1 == 8 pixels high + matrix->setTextWrap(false); // Don't wrap at end of line - will do ourselves + + matrix->setCursor(5, 5); // start at top left, with 5,5 pixel of spacing + uint8_t w = 0; + + for (w=0; wsetTextColor(colorWheel((w*32)+colorWheelOffset)); + matrix->print(str[w]); + } +} +#endif + +uint16_t colorWheel(uint8_t pos) { + if(pos < 85) { + return matrix->color565(pos * 3, 255 - pos * 3, 0); + } else if(pos < 170) { + pos -= 85; + return matrix->color565(255 - pos * 3, 0, pos * 3); + } else { + pos -= 170; + return matrix->color565(0, pos * 3, 255 - pos * 3); + } +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/main.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/main.h new file mode 100644 index 0000000..a1310f4 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/PIO_TestPatterns/src/main.h @@ -0,0 +1,10 @@ + +#include + +#define BAUD_RATE 115200 // serial debug port baud rate + +void buffclear(CRGB *buf); +uint16_t XY16( uint16_t x, uint16_t y); +void mxfill(CRGB *leds); +uint16_t colorWheel(uint8_t pos); +void drawText(int colorWheelOffset); \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/README.md new file mode 100644 index 0000000..5197413 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/examples/README.md @@ -0,0 +1,14 @@ +| Example Name |Description | +|--|--| +|1_SimpleTestShapes |Example for new starters - how to display basic shapes. | +|2_PatternPlasma |Example for new starters - how to display a cool plasma pattern. | +|3_FM6126Panel |Example for new starters - how to initialise FM6126/FM6126A panels with this library. +|AnimatedGIFPanel |Using Larry Bank's GIF Decoder to display animated GIFs. | +|AuroraDemo |Simple example demonstrating various animated effects. | +|BitmapIcons |Simple example of how to display a bitmap image to the display. | +|ChainedPanels |Popular example on how to use the 'VirtualMatrixPanel' class to chain multiple LED Matrix Panels to form a much bigger display! Refer to the README within this example's folder! | +|ChainedPanelsAuroraDemo |As above, but showing a large trippy plasma animation. | +|ChainedPanelsScreenBuffer |Using the same 'VirtualMatrixPanel' class but also implementing a FastLED off-screen pixel buffer to do cool stuff. | +|One_Quarter_1_4_ScanPanel |Using this library with a 32w x 16h 1/4 Scan LED Matrix Panel. Custom co-ordinate remapping logic required. NOT WORKING. | +|One_Eighth_1_8_ScanPanel |Using this library with a 64w x 32h 1/8 Scan LED Matrix Panel. Custom co-ordinate remapping logic required. +|PIO_TestPatterns |Non-Arduino example of how to display basic shapes. | diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/image.jpg b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/image.jpg new file mode 100644 index 0000000..f0c9cbe Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/image.jpg differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/keywords.txt b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/keywords.txt new file mode 100644 index 0000000..b6ccefa --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/keywords.txt @@ -0,0 +1,18 @@ +RGB64x32MatrixPanel_I2S_DMA KEYWORD1 +MatrixPanel_I2S_DMA KEYWORD1 +Layer KEYWORD1 +fillScreen KEYWORD2 +clearScreen KEYWORD2 +fillScreenRGB888 KEYWORD2 +drawPixelRGB565 KEYWORD2 +drawPixelRGB888 KEYWORD2 +drawPixelRGB24 KEYWORD2 +drawIcon KEYWORD2 +color444 KEYWORD2 +color565 KEYWORD2 +color333 KEYWORD2 +flipDMABuffer KEYWORD2 +showDMABuffer KEYWORD2 +setPanelBrightness KEYWORD2 +setMinRefreshRate KEYWORD2 +RGB24 KEYWORD1 \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/library.json b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/library.json new file mode 100644 index 0000000..265c783 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/library.json @@ -0,0 +1,41 @@ +{ + "name": "ESP32 HUB75 LED MATRIX PANEL DMA Display", + "version": "3.0.8", + "description": "An Adafruit GFX compatible library for LED matrix modules which uses DMA for ultra-fast refresh rates and therefore very low CPU usage.", + "keywords": "hub75, esp32, esp32s2, esp32s3, display, dma, rgb matrix", + "repository": { + "type": "git", + "url": "https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git" + }, + "authors": { + "name": "Mr Faptastic", + "url": "https://github.com/mrfaptastic/" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "platforms": [ + "espressif32" + ], + "headers": [ + "ESP32-HUB75-MatrixPanel-I2S-DMA.h", "ESP32-VirtualMatrixPanel-I2S-DMA.h" + ], + "examples": [ + { + "name": "SimpleTestShapes", + "base": "examples/1_SimpleTestShapes", + "files": [ + "1_SimpleTestShapes.ino" + ] + }, + { + "name": "Plasma Pattern", + "base": "examples/2_PatternPlasma", + "files": [ + "2_PatternPlasma.ino", + "README.md" + ] + } + ] +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/library.properties b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/library.properties new file mode 100644 index 0000000..99d444f --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/library.properties @@ -0,0 +1,9 @@ +name=ESP32 HUB75 LED MATRIX PANEL DMA Display +version= 3.0.8 +author=Faptastic +maintainer=Faptastic +sentence=HUB75 LED Matrix Library for ESP32, ESP32-S2 and ESP32-S3 +paragraph=An Adafruit GFX compatible library for LED matrix modules which uses DMA for ultra-fast refresh rates and therefore very low CPU usage. +category=Display +url=https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA +architectures=esp32,esp32s2,esp32s3 diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp new file mode 100644 index 0000000..12555b9 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp @@ -0,0 +1,953 @@ +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" + +#if defined(SPIRAM_DMA_BUFFER) +// Sprite_TM saves the day again... +// https://www.esp32.com/viewtopic.php?f=2&t=30584 +#include "rom/cache.h" +#endif + +/* This replicates same function in rowBitStruct, but due to induced inlining it might be MUCH faster + * when used in tight loops while method from struct could be flushed out of instruction cache between + * loop cycles do NOT forget about buff_id param if using this. + */ +// #define getRowDataPtr(row, _dpth, buff_id) &(dma_buff.rowBits[row]->data[_dpth * dma_buff.rowBits[row]->width + buff_id*(dma_buff.rowBits[row]->width * dma_buff.rowBits[row]->colour_depth)]) + +// BufferID is now ignored, seperate global pointer pointer! +#define getRowDataPtr(row, _dpth, buff_id) &(fb->rowBits[row]->data[_dpth * fb->rowBits[row]->width]) + +/* We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel + * 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering + * Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual + */ +#if defined(ESP32_THE_ORIG) +#define ESP32_TX_FIFO_POSITION_ADJUST(x_coord) (((x_coord)&1U) ? (x_coord - 1) : (x_coord + 1)) +#else +#define ESP32_TX_FIFO_POSITION_ADJUST(x_coord) x_coord +#endif + +/* This library is designed to take an 8 bit / 1 byte value (0-255) for each R G B colour sub-pixel. + * The PIXEL_COLOR_DEPTH_BITS should always be '8' as a result. + * However, if the library is to be used with lower colour depth (i.e. 6 bit colour), then we need to ensure the 8-bit value passed to the colour masking + * is adjusted accordingly to ensure the LSB's are shifted left to MSB, by the difference. Otherwise the colours will be all screwed up. + */ +#define PIXEL_COLOR_MASK_BIT(color_depth_index, mask_offset) (1 << (color_depth_index + mask_offset)) + +bool MatrixPanel_I2S_DMA::allocateDMAmemory() +{ + + ESP_LOGI("I2S-DMA", "Free heap: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + ESP_LOGI("I2S-DMA", "Free SPIRAM: %d", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + + // Alright, theoretically we should be OK, so let us do this, so + // lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place) + ESP_LOGI("I2S-DMA", "allocating rowBitStructs with pixel_color_depth_bits of %d", m_cfg.getPixelColorDepthBits()); + // iterate through number of rows, allocate memory for each + size_t allocated_fb_memory = 0; + + int fbs_required = (m_cfg.double_buff) ? 2 : 1; + for (int fb = 0; fb < (fbs_required); fb++) + { + frame_buffer[fb].rowBits.reserve(ROWS_PER_FRAME); + + for (int malloc_num = 0; malloc_num < ROWS_PER_FRAME; malloc_num++) + { + auto ptr = std::make_shared(PIXELS_PER_ROW, m_cfg.getPixelColorDepthBits(), m_cfg.double_buff); + + if (ptr->data == nullptr) + { + ESP_LOGE("I2S-DMA", "CRITICAL ERROR: Not enough memory for requested colour depth! Please reduce pixel_color_depth_bits value.\r\n"); + ESP_LOGE("I2S-DMA", "Could not allocate rowBitStruct %d!.\r\n", malloc_num); + + return false; + // TODO: should we release all previous rowBitStructs here??? + } + + allocated_fb_memory += ptr->getColorDepthSize(); // byte required to display all colour depths for the rows shown at the same time + frame_buffer[fb].rowBits.emplace_back(ptr); // save new rowBitStruct pointer into rows vector + ++frame_buffer[fb].rows; + } + } + ESP_LOGI("I2S-DMA", "Allocating %d bytes memory for DMA BCM framebuffer(s).", allocated_fb_memory); + + // calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate + +//#define FORCE_COLOR_DEPTH 1 + +#if !defined(FORCE_COLOR_DEPTH) + + ESP_LOGI("I2S-DMA", "Minimum visual refresh rate (scan rate from panel top to bottom) requested: %d Hz", m_cfg.min_refresh_rate); + + while (1) + { + int psPerClock = 1000000000000UL / m_cfg.i2sspeed; + int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000; + + // add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions... + int nsPerRow = m_cfg.getPixelColorDepthBits() * nsPerLatch; + + // add time to shift out MSBs + for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) + nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * (m_cfg.getPixelColorDepthBits() - i) * nsPerLatch; + + int nsPerFrame = nsPerRow * ROWS_PER_FRAME; + int actualRefreshRate = 1000000000UL / (nsPerFrame); + calculated_refresh_rate = actualRefreshRate; + + ESP_LOGW("I2S-DMA", "lsbMsbTransitionBit of %d gives %d Hz refresh rate.", lsbMsbTransitionBit, actualRefreshRate); + + if (actualRefreshRate > m_cfg.min_refresh_rate) + break; + + if (lsbMsbTransitionBit < m_cfg.getPixelColorDepthBits() - 1) + lsbMsbTransitionBit++; + else + break; + } + + if (lsbMsbTransitionBit > 0) + { + ESP_LOGW("I2S-DMA", "lsbMsbTransitionBit of %d used to achieve refresh rate of %d Hz. Percieved colour depth to the eye may be reduced.", lsbMsbTransitionBit, m_cfg.min_refresh_rate); + } + + ESP_LOGI("I2S-DMA", "DMA has pixel_color_depth_bits of %d", m_cfg.getPixelColorDepthBits() - lsbMsbTransitionBit); + +#endif + + /*** + * Step 2a: lsbMsbTransition bit is now finalised - recalculate the DMA descriptor count required, which is used for + * memory allocation of the DMA linked list memory structure. + */ + int numDMAdescriptorsPerRow = 1; + for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) + { + numDMAdescriptorsPerRow += (1 << (i - lsbMsbTransitionBit - 1)); + } + + ESP_LOGI("I2S-DMA", "Recalculated number of DMA descriptors per row: %d", numDMAdescriptorsPerRow); + + // Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists. + // numDMAdescriptorsPerRow is also used to calculate descount which is super important in i2s_parallel_config_t SoC DMA setup. + if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX) + { + + ESP_LOGW("I2S-DMA", "rowBits struct is too large to fit in one DMA transfer payload, splitting required. Adding %d DMA descriptors\n", m_cfg.getPixelColorDepthBits() - 1); + + numDMAdescriptorsPerRow += m_cfg.getPixelColorDepthBits() - 1; + // Note: If numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop. + } + + /*** + * Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output. + */ + + // malloc the DMA linked list descriptors that i2s_parallel will need + int desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME; + + if (m_cfg.double_buff) + { + dma_bus.enable_double_dma_desc(); + } + + dma_bus.allocate_dma_desc_memory(desccount); + + // point FB we can write to, to 0 / dmadesc_a + fb = &frame_buffer[0]; + + // Just os we know + initialized = true; + + return true; + +} // end allocateDMAmemory() + + + +/* +// Version 2.0 March 2023 +int MatrixPanel_I2S_DMA::create_descriptor_links(void *data, size_t size, bool dmadesc_b, bool countonly) +{ + int len = size; + uint8_t *data2 = (uint8_t *)data; + + int n = 0; + while (len) + { + int dmalen = len; + if (dmalen > DMA_MAX) + dmalen = DMA_MAX; + + if (!countonly) + dma_bus.create_dma_desc_link(data2, dmalen, dmadesc_b); + + len -= dmalen; + data2 += dmalen; + n++; + } + + return n; +} +*/ +void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG &_cfg) +{ + + // lldesc_t *previous_dmadesc_a = 0; + // lldesc_t *previous_dmadesc_b = 0; + int current_dmadescriptor_offset = 0; + + // HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the colour_depth. + int num_dma_payload_colour_depths = m_cfg.getPixelColorDepthBits(); + if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX) + { + num_dma_payload_colour_depths = 1; + } + + + // Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) in sequence (top to bottom) + for (int row = 0; row < ROWS_PER_FRAME; row++) + { + // first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all colour bits are displayed once, which takes care of everything below and including LSBMSB_TRANSITION_BIT + // NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two + // link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, dma_buff.rowBits[row]->getDataPtr(), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths)); + // previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; + + dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(0, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(), false); + + if (m_cfg.double_buff) + { + dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(0, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(), true); + } + + current_dmadescriptor_offset++; + + // If the number of pixels per row is too great for the size of a DMA payload, so we need to split what we were going to send above. + if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX) + { + + for (int cd = 1; cd < m_cfg.getPixelColorDepthBits(); cd++) + { + dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(cd, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false); + + if (m_cfg.double_buff) + { + dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(cd, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(1), true); + } + + current_dmadescriptor_offset++; + + } // additional linked list items + } // row depth struct + + for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) + { + // binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc + // because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM) + // we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB + + for (int k = 0; k < (1 << (i - lsbMsbTransitionBit - 1)); k++) + { + dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(i, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false); + + if (m_cfg.double_buff) + { + dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(i, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(1), true); + } + + current_dmadescriptor_offset++; + + } // end colour depth ^ 2 linked list + } // end colour depth loop + + } // end frame rows + + ESP_LOGI("I2S-DMA", "%d DMA descriptors linked to buffer data.", current_dmadescriptor_offset); + + // + // Setup DMA and Output to GPIO + // + auto bus_cfg = dma_bus.config(); // バス設定用の構造体を取得します。 + + bus_cfg.bus_freq = m_cfg.i2sspeed; + bus_cfg.pin_wr = m_cfg.gpio.clk; // WR を接続しているピン番号 + + bus_cfg.pin_d0 = m_cfg.gpio.r1; + bus_cfg.pin_d1 = m_cfg.gpio.g1; + bus_cfg.pin_d2 = m_cfg.gpio.b1; + bus_cfg.pin_d3 = m_cfg.gpio.r2; + bus_cfg.pin_d4 = m_cfg.gpio.g2; + bus_cfg.pin_d5 = m_cfg.gpio.b2; + bus_cfg.pin_d6 = m_cfg.gpio.lat; + bus_cfg.pin_d7 = m_cfg.gpio.oe; + bus_cfg.pin_d8 = m_cfg.gpio.a; + bus_cfg.pin_d9 = m_cfg.gpio.b; + bus_cfg.pin_d10 = m_cfg.gpio.c; + bus_cfg.pin_d11 = m_cfg.gpio.d; + bus_cfg.pin_d12 = m_cfg.gpio.e; + bus_cfg.pin_d13 = -1; + bus_cfg.pin_d14 = -1; + bus_cfg.pin_d15 = -1; + +#if defined(SPIRAM_DMA_BUFFER) + bus_cfg.psram_clk_override = true; +#endif + + dma_bus.config(bus_cfg); + + dma_bus.init(); + + dma_bus.dma_transfer_start(); + + flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled. + + // i2s_parallel_send_dma(ESP32_I2S_DEVICE, &dmadesc_a[0]); + ESP_LOGI("I2S-DMA", "DMA setup completed"); + +} // end initMatrixDMABuff + +/* There are 'bits' set in the frameStruct that we simply don't need to set every single time we change a pixel / DMA buffer co-ordinate. + * For example, the bits that determine the address lines, we don't need to set these every time. Once they're in place, and assuming we + * don't accidentally clear them, then we don't need to set them again. + * So to save processing, we strip this logic out to the absolute bare minimum, which is toggling only the R,G,B pixels (bits) per co-ord. + * + * Critical dependency: That 'updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue)' has been run at least once over the + * entire frameBuffer to ensure all the non R,G,B bitmasks are in place (i.e. like OE, Address Lines etc.) + * + * Note: If you change the brightness with setBrightness() you MUST then clearScreen() and repaint / flush the entire framebuffer. + */ + +/** @brief - Update pixel at specific co-ordinate in the DMA buffer + * this is the main method used to update DMA buffer on pixel-by-pixel level so it must be fast, real fast! + * Let's put it into IRAM to avoid situations when it could be flushed out of instruction cache + * and had to be read from spi-flash over and over again. + * Yes, it is always a tradeoff between memory/speed/size, but compared to DMA-buffer size is not a big deal + * + * Note: Cannot pass a negative co-ord as it makes no sense in the DMA bit array lookup. + */ +void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x_coord, uint16_t y_coord, uint8_t red, uint8_t green, uint8_t blue) +{ + if (!initialized) + return; + + /* 1) Check that the co-ordinates are within range, or it'll break everything big time. + * Valid co-ordinates are from 0 to (MATRIX_XXXX-1) + */ + if (x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) + { + return; + } + + /* LED Brightness Compensation. Because if we do a basic "red & mask" for example, + * we'll NEVER send the dimmest possible colour, due to binary skew. + * i.e. It's almost impossible for colour_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a colour is exactly '1' + * https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ + */ + uint16_t red16, green16, blue16; +#ifndef NO_CIE1931 + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; +#else + red16 = red << 8; + green16 = green << 8; + blue16 = blue << 8; +#endif + + /* When using the drawPixel, we are obviously only changing the value of one x,y position, + * however, the two-scan panels paint TWO lines at the same time + * and this reflects the parallel in-DMA-memory data structure of uint16_t's that are getting + * pumped out at high speed. + * + * So we need to ensure we persist the bits (8 of them) of the uint16_t for the row we aren't changing. + * + * The DMA buffer order has also been reversed (refer to the last code in this function) + * so we have to check for this and check the correct position of the MATRIX_DATA_STORAGE_TYPE + * data. + */ + x_coord = ESP32_TX_FIFO_POSITION_ADJUST(x_coord); + + uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0; + + if (y_coord >= ROWS_PER_FRAME) + { // if we are drawing to the bottom part of the panel + _colourbitoffset = BITS_RGB2_OFFSET; + _colourbitclear = BITMASK_RGB2_CLEAR; + y_coord -= ROWS_PER_FRAME; + } + + // Iterating through colour depth bits, which we assume are 8 bits per RGB subpixel (24bpp) + uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits(); + do + { + --colour_depth_idx; + + uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); + uint16_t RGB_output_bits = 0; + + /* Per the .h file, the order of the output RGB bits is: + * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ + RGB_output_bits |= (bool)(blue16 & mask); // --B + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(red16 & mask); // BGR + RGB_output_bits <<= _colourbitoffset; // shift colour bits to the required position + + // Get the contents at this address, + // it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate + ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id); + + // We need to update the correct uint16_t word in the rowBitStruct array pointing to a specific pixel at X - coordinate + p[x_coord] &= _colourbitclear; // reset RGB bits + p[x_coord] |= RGB_output_bits; // set new RGB bits + +#if defined(SPIRAM_DMA_BUFFER) + Cache_WriteBack_Addr((uint32_t)&p[x_coord], sizeof(ESP32_I2S_DMA_STORAGE_TYPE)); +#endif + + } while (colour_depth_idx); // end of colour depth loop (8) +} // updateMatrixDMABuffer (specific co-ords change) + +/* Update the entire buffer with a single specific colour - quicker */ +void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue) +{ + if (!initialized) + return; + + /* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */ + uint16_t red16, green16, blue16; +#ifndef NO_CIE1931 + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; +#else + red16 = red << 8; + green16 = green << 8; + blue16 = blue << 8; +#endif + + for (uint8_t colour_depth_idx = 0; colour_depth_idx < m_cfg.getPixelColorDepthBits(); colour_depth_idx++) // colour depth - 8 iterations + { + // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer + uint16_t RGB_output_bits = 0; + + uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); + + /* Per the .h file, the order of the output RGB bits is: + * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ + RGB_output_bits |= (bool)(blue16 & mask); // --B + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(red16 & mask); // BGR + + // Duplicate and shift across so we have have 6 populated bits of RGB1 and RGB2 pin values suitable for DMA buffer + RGB_output_bits |= RGB_output_bits << BITS_RGB2_OFFSET; // BGRBGR + + // Serial.printf("Fill with: 0x%#06x\n", RGB_output_bits); + + // iterate rows + int matrix_frame_parallel_row = fb->rowBits.size(); + do + { + --matrix_frame_parallel_row; + + // The destination for the pixel row bitstream + ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(matrix_frame_parallel_row, colour_depth_idx, back_buffer_id); + + // iterate pixels in a row + int x_coord = fb->rowBits[matrix_frame_parallel_row]->width; + do + { + --x_coord; + p[x_coord] &= BITMASK_RGB12_CLEAR; // reset colour bits + p[x_coord] |= RGB_output_bits; // set new colour bits + +#if defined(SPIRAM_DMA_BUFFER) + Cache_WriteBack_Addr((uint32_t)&p[x_coord], sizeof(ESP32_I2S_DMA_STORAGE_TYPE)); +#endif + + } while (x_coord); + + } while (matrix_frame_parallel_row); // end row iteration + } // colour depth loop (8) +} // updateMatrixDMABuffer (full frame paint) + +/** + * @brief - clears and reinitializes colour/control data in DMA buffs + * When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits. + * Those control bits are constants during the entire DMA sweep and never changed when updating just pixel colour data + * so we could set it once on DMA buffs initialization and forget. + * This effectively clears buffers to blank BLACK and makes it ready to display output. + * (Brightness control via OE bit manipulation is another case) - this must be done as well seperately! + */ +void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id) +{ + if (!initialized) + return; + + frameStruct *fb = &frame_buffer[_buff_id]; + + // we start with iterating all rows in dma_buff structure + int row_idx = fb->rowBits.size(); + do + { + --row_idx; + + ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(0, -1); // set pointer to the HEAD of a buffer holding data for the entire matrix row + + ESP32_I2S_DMA_STORAGE_TYPE abcde = (ESP32_I2S_DMA_STORAGE_TYPE)row_idx; + abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12 + + // get last pixel index in a row of all colourdepths + int x_pixel = fb->rowBits[row_idx]->width * fb->rowBits[row_idx]->colour_depth; + // Serial.printf(" from pixel %d, ", x_pixel); + + // fill all x_pixels except colour_index[0] (LSB) ones, this also clears all colour data to 0's black + do + { + --x_pixel; + + if (m_cfg.driver == HUB75_I2S_CFG::SM5266P) + { + // modifications here for row shift register type SM5266P + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164 + row[x_pixel] = abcde & (0x18 << BITS_ADDR_OFFSET); // mask out the bottom 3 bits which are the clk di bk inputs + } + else + { + row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel)] = abcde; + } + + } while (x_pixel != fb->rowBits[row_idx]->width && x_pixel); + + // colour_index[0] (LSB) x_pixels must be "marked" with a previous's row address, 'cause it is used to display + // previous row while we pump in LSB's for a new row + abcde = ((ESP32_I2S_DMA_STORAGE_TYPE)row_idx - 1) << BITS_ADDR_OFFSET; + do + { + --x_pixel; + + if (m_cfg.driver == HUB75_I2S_CFG::SM5266P) + { + // modifications here for row shift register type SM5266P + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164 + row[x_pixel] = abcde & (0x18 << BITS_ADDR_OFFSET); // mask out the bottom 3 bits which are the clk di bk inputs + } + else + { + row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel)] = abcde; + } + + } while (x_pixel); + + // modifications here for row shift register type SM5266P + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164 + if (m_cfg.driver == HUB75_I2S_CFG::SM5266P) + { + uint16_t serialCount; + uint16_t latch; + x_pixel = fb->rowBits[row_idx]->width - 16; // come back 8*2 pixels to allow for 8 writes + serialCount = 8; + do + { + serialCount--; + latch = row[x_pixel] | (((((ESP32_I2S_DMA_STORAGE_TYPE)row_idx) % 8) == serialCount) << 1) << BITS_ADDR_OFFSET; // data on 'B' + row[x_pixel++] = latch | (0x05 << BITS_ADDR_OFFSET); // clock high on 'A'and BK high for update + row[x_pixel++] = latch | (0x04 << BITS_ADDR_OFFSET); // clock low on 'A'and BK high for update + } while (serialCount); + } // end SM5266P + + // let's set LAT/OE control bits for specific pixels in each colour_index subrows + // Need to consider the original ESP32's (WROOM) DMA TX FIFO reordering of bytes... + uint8_t colouridx = fb->rowBits[row_idx]->colour_depth; + do + { + --colouridx; + + // switch pointer to a row for a specific colour index + row = fb->rowBits[row_idx]->getDataPtr(colouridx, -1); + + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - 1)] |= BIT_LAT; // -1 pixel to compensate array index starting at 0 + + // ESP32_TX_FIFO_POSITION_ADJUST(dma_buff.rowBits[row_idx]->width - 1) + + // need to disable OE before/after latch to hide row transition + // Should be one clock or more before latch, otherwise can get ghosting + uint8_t _blank = m_cfg.latch_blanking; + do + { + --_blank; + + row[ESP32_TX_FIFO_POSITION_ADJUST(0 + _blank)] |= BIT_OE; // disable output + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - 1)] |= BIT_OE; // disable output + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - _blank - 1)] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 + + } while (_blank); + + } while (colouridx); + +#if defined(SPIRAM_DMA_BUFFER) + Cache_WriteBack_Addr((uint32_t)row, fb->rowBits[row_idx]->getColorDepthSize()); +#endif + + } while (row_idx); +} + +/** + * @brief - reset OE bits in DMA buffer in a way to control brightness + * @param brt - brightness level from 0 to 255 - NOT MATRIX_WIDTH + * @param _buff_id - buffer id to control + */ +void MatrixPanel_I2S_DMA::brtCtrlOEv2(uint8_t brt, const int _buff_id) +{ + + if (!initialized) + return; + + frameStruct *fb = &frame_buffer[_buff_id]; + + uint8_t _blank = m_cfg.latch_blanking; // don't want to inadvertantly blast over this + uint8_t _depth = fb->rowBits[0]->colour_depth; + uint16_t _width = fb->rowBits[0]->width; + + // start with iterating all rows in dma_buff structure + int row_idx = fb->rowBits.size(); + do + { + --row_idx; + + // let's set OE control bits for specific pixels in each color_index subrows + uint8_t colouridx = _depth; + do + { + --colouridx; + + char bitplane = (2 * _depth - colouridx) % _depth; + char bitshift = (_depth - lsbMsbTransitionBit - 1) >> 1; + + char rightshift = std::max(bitplane - bitshift - 2, 0); + // calculate the OE disable period by brightness, and also blanking + int brightness_in_x_pixels = ((_width - _blank) * brt) >> (7 + rightshift); + brightness_in_x_pixels = (brightness_in_x_pixels >> 1) | (brightness_in_x_pixels & 1); + + // switch pointer to a row for a specific color index + ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(colouridx, _buff_id); + + // define range of Output Enable on the center of the row + int x_coord_max = (_width + brightness_in_x_pixels + 1) >> 1; + int x_coord_min = (_width - brightness_in_x_pixels + 0) >> 1; + int x_coord = _width; + do + { + --x_coord; + + // (the check is already including "blanking" ) + if (x_coord >= x_coord_min && x_coord < x_coord_max) + { + row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] &= BITMASK_OE_CLEAR; + } + else + { + row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] |= BIT_OE; // Disable output after this point. + } + + } while (x_coord); + + } while (colouridx); + +// switch pointer to a row for a specific colour index +#if defined(SPIRAM_DMA_BUFFER) + ESP32_I2S_DMA_STORAGE_TYPE *row_hack = fb->rowBits[row_idx]->getDataPtr(0, _buff_id); + //Cache_WriteBack_Addr((uint32_t)row_hack, sizeof(ESP32_I2S_DMA_STORAGE_TYPE) * ((fb->rowBits[row_idx]->width * fb->rowBits[row_idx]->colour_depth) - 1)); + Cache_WriteBack_Addr((uint32_t)row_hack, fb->rowBits[row_idx]->getColorDepthSize()); +#endif + } while (row_idx); +} + +/* + * overload for compatibility + */ + +bool MatrixPanel_I2S_DMA::begin(int r1, int g1, int b1, int r2, int g2, int b2, int a, int b, int c, int d, int e, int lat, int oe, int clk) +{ + if (initialized) + return true; + // RGB + m_cfg.gpio.r1 = r1; + m_cfg.gpio.g1 = g1; + m_cfg.gpio.b1 = b1; + m_cfg.gpio.r2 = r2; + m_cfg.gpio.g2 = g2; + m_cfg.gpio.b2 = b2; + + // Line Select + m_cfg.gpio.a = a; + m_cfg.gpio.b = b; + m_cfg.gpio.c = c; + m_cfg.gpio.d = d; + m_cfg.gpio.e = e; + + // Clock & Control + m_cfg.gpio.lat = lat; + m_cfg.gpio.oe = oe; + m_cfg.gpio.clk = clk; + + return begin(); +} + +bool MatrixPanel_I2S_DMA::begin(const HUB75_I2S_CFG &cfg) +{ + if (initialized) + return true; + + if (!setCfg(cfg)) + return false; + + return begin(); +} + +/** + * @brief - Sets how many clock cycles to blank OE before/after LAT signal change + * @param uint8_t pulses - clocks before/after OE + * default is DEFAULT_LAT_BLANKING + * Max is MAX_LAT_BLANKING + * @returns - new value for m_cfg.latch_blanking + */ +uint8_t MatrixPanel_I2S_DMA::setLatBlanking(uint8_t pulses) +{ + if (pulses > MAX_LAT_BLANKING) + pulses = MAX_LAT_BLANKING; + + if (!pulses) + pulses = DEFAULT_LAT_BLANKING; + + m_cfg.latch_blanking = pulses; + + // remove brightness var for now. + // setPanelBrightness(brightness); // set brightness to reset OE bits to the values matching new LAT blanking setting + return m_cfg.latch_blanking; +} + +#ifndef NO_FAST_FUNCTIONS +/** + * @brief - update DMA buff drawing horizontal line at specified coordinates + * @param x_coord - line start coordinate x + * @param y_coord - line start coordinate y + * @param l - line length + * @param r,g,b, - RGB888 colour + */ +void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue) +{ + if (!initialized) + return; + + if ((x_coord + l) < 1 || y_coord < 0 || l < 1 || x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) + return; + + l = x_coord < 0 ? l + x_coord : l; + x_coord = x_coord < 0 ? 0 : x_coord; + + l = ((x_coord + l) >= PIXELS_PER_ROW) ? (PIXELS_PER_ROW - x_coord) : l; + + // if (x_coord+l > PIXELS_PER_ROW) + // l = PIXELS_PER_ROW - x_coord + 1; // reset width to end of row + + /* LED Brightness Compensation */ + uint16_t red16, green16, blue16; +#ifndef NO_CIE1931 + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; +#else + red16 = red << 8; + green16 = green << 8; + blue16 = blue << 8; +#endif + + uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0; + + if (y_coord >= ROWS_PER_FRAME) + { // if we are drawing to the bottom part of the panel + _colourbitoffset = BITS_RGB2_OFFSET; + _colourbitclear = BITMASK_RGB2_CLEAR; + y_coord -= ROWS_PER_FRAME; + } + + // Iterating through colour depth bits (8 iterations) + uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits(); + do + { + --colour_depth_idx; + + // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer + uint16_t RGB_output_bits = 0; + // uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); + // #if PIXEL_COLOR_DEPTH_BITS < 8 + // uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel) + // #else + // uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit colour (8 bits per RGB subpixel) + // #endif + uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); + + /* Per the .h file, the order of the output RGB bits is: + * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ + RGB_output_bits |= (bool)(blue16 & mask); // --B + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(red16 & mask); // BGR + RGB_output_bits <<= _colourbitoffset; // shift color bits to the required position + + // Get the contents at this address, + // it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate + ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[y_coord]->getDataPtr(colour_depth_idx, back_buffer_id); + // inlined version works slower here, dunno why :( + // ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id); + + int16_t _l = l; + do + { // iterate pixels in a row + int16_t _x = x_coord + --_l; + + /* + #if defined(ESP32_THE_ORIG) + // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering + uint16_t &v = p[_x & 1U ? --_x : ++_x]; + #else + // ESP 32 doesn't need byte flipping for TX FIFO. + uint16_t &v = p[_x]; + #endif + */ + uint16_t &v = p[ESP32_TX_FIFO_POSITION_ADJUST(_x)]; + + v &= _colourbitclear; // reset colour bits + v |= RGB_output_bits; // set new colour bits + } while (_l); // iterate pixels in a row + } while (colour_depth_idx); // end of colour depth loop (8) +} // hlineDMA() + +/** + * @brief - update DMA buff drawing vertical line at specified coordinates + * @param x_coord - line start coordinate x + * @param y_coord - line start coordinate y + * @param l - line length + * @param r,g,b, - RGB888 colour + */ +void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue) +{ + if (!initialized) + return; + + if (x_coord < 0 || (y_coord + l) < 1 || l < 1 || x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) + return; + + l = y_coord < 0 ? l + y_coord : l; + y_coord = y_coord < 0 ? 0 : y_coord; + + // check for a length that goes beyond the height of the screen! Array out of bounds dma memory changes = screwed output #163 + l = ((y_coord + l) >= m_cfg.mx_height) ? (m_cfg.mx_height - y_coord) : l; + // if (y_coord + l > m_cfg.mx_height) + /// l = m_cfg.mx_height - y_coord + 1; // reset width to end of col + + /* LED Brightness Compensation */ + uint16_t red16, green16, blue16; +#ifndef NO_CIE1931 + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; +#else + red16 = red << 8; + green16 = green << 8; + blue16 = blue << 8; +#endif + + /* + #if defined(ESP32_THE_ORIG) + // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering + x_coord & 1U ? --x_coord : ++x_coord; + #endif + */ + x_coord = ESP32_TX_FIFO_POSITION_ADJUST(x_coord); + + uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits(); + do + { // Iterating through colour depth bits (8 iterations) + --colour_depth_idx; + + // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer + // uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); + // #if PIXEL_COLOR_DEPTH_BITS < 8 + // uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel) + // #else + // uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit colour (8 bits per RGB subpixel) + // #endif + + uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); + uint16_t RGB_output_bits = 0; + + /* Per the .h file, the order of the output RGB bits is: + * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ + RGB_output_bits |= (bool)(blue16 & mask); // --B + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(red16 & mask); // BGR + + int16_t _l = 0, _y = y_coord; + uint16_t _colourbitclear = BITMASK_RGB1_CLEAR; + do + { // iterate pixels in a column + + if (_y >= ROWS_PER_FRAME) + { // if y-coord overlapped bottom-half panel + _y -= ROWS_PER_FRAME; + _colourbitclear = BITMASK_RGB2_CLEAR; + RGB_output_bits <<= BITS_RGB2_OFFSET; + } + + // Get the contents at this address, + // it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate + // ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(_y, colour_depth_idx, back_buffer_id); + ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[_y]->getDataPtr(colour_depth_idx, back_buffer_id); + + p[x_coord] &= _colourbitclear; // reset RGB bits + p[x_coord] |= RGB_output_bits; // set new RGB bits + ++_y; + } while (++_l != l); // iterate pixels in a col + } while (colour_depth_idx); // end of colour depth loop (8) +} // vlineDMA() + +/** + * @brief - update DMA buff drawing a rectangular at specified coordinates + * this works much faster than multiple consecutive per-pixel calls to updateMatrixDMABuffer() + * @param int16_t x, int16_t y - coordinates of a top-left corner + * @param int16_t w, int16_t h - width and height of a rectangular, min is 1 px + * @param uint8_t r - RGB888 colour + * @param uint8_t g - RGB888 colour + * @param uint8_t b - RGB888 colour + */ +void MatrixPanel_I2S_DMA::fillRectDMA(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b) +{ + + // h-lines are >2 times faster than v-lines + // so will use it only for tall rects with h >2w + if (h > 2 * w) + { + // draw using v-lines + do + { + --w; + vlineDMA(x + w, y, h, r, g, b); + } while (w); + } + else + { + // draw using h-lines + do + { + --h; + hlineDMA(x, y + h, w, r, g, b); + } while (h); + } +} + +#endif // NO_FAST_FUNCTIONS diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h new file mode 100644 index 0000000..7332db6 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h @@ -0,0 +1,1020 @@ +#ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA +#define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA +/***************************************************************************************/ +/* Core ESP32 hardware / idf includes! */ +#include +#include +#include +#include +#include "esp_attr.h" + +// #include +#include "platforms/platform_detect.hpp" + +#ifdef USE_GFX_ROOT +#include +#include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root +#elif !defined NO_GFX +#include "Adafruit_GFX.h" // Adafruit class with all the other stuff +#endif + +/******************************************************************************************* + * COMPILE-TIME OPTIONS - MUST BE PROVIDED as part of PlatformIO project build_flags. * + * Changing the values just here won't work - as defines needs to persist beyond the scope * + * of just this file. * + *******************************************************************************************/ +/* Do NOT build additional methods optimized for fast drawing, + * i.e. Adafruits drawFastHLine, drawFastVLine, etc... */ +// #define NO_FAST_FUNCTIONS + +// #define NO_CIE1931 + +/* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT. + * + * This library has been tested with a 64x32 and 64x64 RGB panels. + * If you want to chain two or more of these horizontally to make a 128x32 panel + * you can do so with the cable and then set the CHAIN_LENGTH to '2'. + * + * Also, if you use a 64x64 panel, then set the MATRIX_HEIGHT to '64' and an E_PIN; it will work! + * + * All of this is memory permitting of course (dependant on your sketch etc.) ... + * + */ +#ifndef MATRIX_WIDTH +#define MATRIX_WIDTH 64 // Single panel of 64 pixel width +#endif + +#ifndef MATRIX_HEIGHT +#define MATRIX_HEIGHT 32 // CHANGE THIS VALUE to 64 IF USING 64px HIGH panel(s) with E PIN +#endif + +#ifndef CHAIN_LENGTH +#define CHAIN_LENGTH 1 // Number of modules chained together, i.e. 4 panels chained result in virtualmatrix 64x4=256 px long +#endif + +// Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but +// given we only map to 14 physical output wires/bits, we waste 2 bits. + +/***************************************************************************************/ +/* Do not change definitions below unless you pretty sure you know what you are doing! */ + +// keeping a check sine it was possibe to set it previously +#ifdef MATRIX_ROWS_IN_PARALLEL +#pragma message "You are not supposed to set MATRIX_ROWS_IN_PARALLEL. Setting it back to default." +#undef MATRIX_ROWS_IN_PARALLEL +#endif +#define MATRIX_ROWS_IN_PARALLEL 2 + +// 8bit per RGB color = 24 bit/per pixel, +// can be extended to offer deeper colors, or +// might be reduced to save DMA RAM +#ifdef PIXEL_COLOUR_DEPTH_BITS +#define PIXEL_COLOR_DEPTH_BITS PIXEL_COLOUR_DEPTH_BITS +#endif + +// support backwarts compatibility +#ifdef PIXEL_COLOR_DEPTH_BITS +#define PIXEL_COLOR_DEPTH_BITS_DEFAULT PIXEL_COLOR_DEPTH_BITS +#else +#define PIXEL_COLOR_DEPTH_BITS_DEFAULT 8 +#endif + +#define PIXEL_COLOR_DEPTH_BITS_MAX 12 + +/***************************************************************************************/ +/* Definitions below should NOT be ever changed without rewriting library logic */ +#define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // DMA output of one uint16_t at a time. +#define CLKS_DURING_LATCH 0 // Not (yet) used. + +// Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration) +#define BITS_RGB1_OFFSET 0 // Start point of RGB_X1 bits +#define BIT_R1 (1 << 0) +#define BIT_G1 (1 << 1) +#define BIT_B1 (1 << 2) + +// Panel Lower half RGB +#define BITS_RGB2_OFFSET 3 // Start point of RGB_X2 bits +#define BIT_R2 (1 << 3) +#define BIT_G2 (1 << 4) +#define BIT_B2 (1 << 5) + +// Panel Control Signals +#define BIT_LAT (1 << 6) +#define BIT_OE (1 << 7) + +// Panel GPIO Pin Addresses (A, B, C, D etc..) +#define BITS_ADDR_OFFSET 8 // Start point of address bits +#define BIT_A (1 << 8) +#define BIT_B (1 << 9) +#define BIT_C (1 << 10) +#define BIT_D (1 << 11) +#define BIT_E (1 << 12) + +// BitMasks are pre-computed based on the above #define's for performance. +#define BITMASK_RGB1_CLEAR (0b1111111111111000) // inverted bitmask for R1G1B1 bit in pixel vector +#define BITMASK_RGB2_CLEAR (0b1111111111000111) // inverted bitmask for R2G2B2 bit in pixel vector +#define BITMASK_RGB12_CLEAR (0b1111111111000000) // inverted bitmask for R1G1B1R2G2B2 bit in pixel vector +#define BITMASK_CTRL_CLEAR (0b1110000000111111) // inverted bitmask for control bits ABCDE,LAT,OE in pixel vector +#define BITMASK_OE_CLEAR (0b1111111101111111) // inverted bitmask for control bit OE in pixel vector + +// How many clock cycles to blank OE before/after LAT signal change, default is 2 clocks +#define DEFAULT_LAT_BLANKING 2 + +// Max clock cycles to blank OE before/after LAT signal change +#define MAX_LAT_BLANKING 4 + +/***************************************************************************************/ + +/** @brief - Structure holds raw DMA data to drive TWO full rows of pixels spanning through all chained modules + * Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer must be word-aligned + */ +struct rowBitStruct +{ + const size_t width; + const uint8_t colour_depth; + const bool double_buff; + ESP32_I2S_DMA_STORAGE_TYPE *data; + + /** @brief + * Returns size (in bytes) of row of data vectorfor a SINGLE buff for the number of colour depths requested + * + * default - Returns full data vector size for a SINGLE buff. + * You should only pass either PIXEL_COLOR_DEPTH_BITS or '1' to this + * + */ + size_t getColorDepthSize(uint8_t _dpth = 0) + { + if (!_dpth) + _dpth = colour_depth; + return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE); + }; + + /** @brief + * Returns pointer to the row's data vector beginning at pixel[0] for _dpth colour bit + * + * NOTE: this call might be very slow in loops. Due to poor instruction caching in esp32 it might be required a reread from flash + * every loop cycle, better use inlined #define instead in such cases + */ + // inline ESP32_I2S_DMA_STORAGE_TYPE* getDataPtr(const uint8_t _dpth=0, const bool buff_id=0) { return &(data[_dpth*width + buff_id*(width*colour_depth)]); }; + + // BUFFER ID VALUE IS NOW IGNORED!!!! + inline ESP32_I2S_DMA_STORAGE_TYPE *getDataPtr(const uint8_t _dpth = 0, const bool buff_id = 0) { return &(data[_dpth * width]); }; + + // constructor - allocates DMA-capable memory to hold the struct data + rowBitStruct(const size_t _width, const uint8_t _depth, const bool _dbuff) : width(_width), colour_depth(_depth), double_buff(_dbuff) + { + + // #if defined(SPIRAM_FRAMEBUFFER) && defined (CONFIG_IDF_TARGET_ESP32S3) +#if defined(SPIRAM_DMA_BUFFER) + + // data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, size()+size()*double_buff, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + // No longer have double buffer in the same struct - have a different struct + data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, getColorDepthSize(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +#else + // data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); + + // No longer have double buffer in the same struct - have a different struct + data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc(getColorDepthSize(), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); + +#endif + } + ~rowBitStruct() { delete data; } +}; + +/* frameStruct + * Note: A 'frameStruct' contains ALL the data for a full-frame (i.e. BOTH 2x16-row frames are + * are contained in parallel within the one uint16_t that is sent in parallel to the HUB75). + * + * This structure isn't actually allocated in one memory block anymore, as the library now allocates + * memory per row (per rowBits) instead. + */ +struct frameStruct +{ + uint8_t rows = 0; // number of rows held in current frame, not used actually, just to keep the idea of struct + std::vector> rowBits; +}; + +/***************************************************************************************/ +// C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ +// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e +// need to make sure this would end up in RAM for fastest access +#ifndef NO_CIE1931 +/* +static const uint8_t DRAM_ATTR lumConvTab[]={ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 156, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 192, 194, 196, 198, 200, 203, 205, 207, 209, 212, 214, 216, 218, 221, 223, 226, 228, 230, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255, 255}; +*/ +// This is 16-bit version of the table, +// the constants taken from the example in the article above, each entries subtracted from 65535: +static const uint16_t DRAM_ATTR lumConvTab[] = { + 0, 27, 56, 84, 113, 141, 170, 198, 227, 255, 284, 312, 340, 369, 397, 426, + 454, 483, 511, 540, 568, 597, 626, 657, 688, 720, 754, 788, 824, 860, 898, 936, + 976, 1017, 1059, 1102, 1146, 1191, 1238, 1286, 1335, 1385, 1436, 1489, 1543, 1598, 1655, 1713, + 1772, 1833, 1895, 1958, 2023, 2089, 2156, 2225, 2296, 2368, 2441, 2516, 2592, 2670, 2750, 2831, + 2914, 2998, 3084, 3171, 3260, 3351, 3443, 3537, 3633, 3731, 3830, 3931, 4034, 4138, 4245, 4353, + 4463, 4574, 4688, 4803, 4921, 5040, 5161, 5284, 5409, 5536, 5665, 5796, 5929, 6064, 6201, 6340, + 6482, 6625, 6770, 6917, 7067, 7219, 7372, 7528, 7687, 7847, 8010, 8174, 8341, 8511, 8682, 8856, + 9032, 9211, 9392, 9575, 9761, 9949, 10139, 10332, 10527, 10725, 10925, 11127, 11332, 11540, 11750, 11963, + 12178, 12395, 12616, 12839, 13064, 13292, 13523, 13757, 13993, 14231, 14473, 14717, 14964, 15214, 15466, 15722, + 15980, 16240, 16504, 16771, 17040, 17312, 17587, 17865, 18146, 18430, 18717, 19006, 19299, 19595, 19894, 20195, + 20500, 20808, 21119, 21433, 21750, 22070, 22393, 22720, 23049, 23382, 23718, 24057, 24400, 24745, 25094, 25446, + 25802, 26160, 26522, 26888, 27256, 27628, 28004, 28382, 28765, 29150, 29539, 29932, 30328, 30727, 31130, 31536, + 31946, 32360, 32777, 33197, 33622, 34049, 34481, 34916, 35354, 35797, 36243, 36692, 37146, 37603, 38064, 38528, + 38996, 39469, 39945, 40424, 40908, 41395, 41886, 42382, 42881, 43383, 43890, 44401, 44916, 45434, 45957, 46484, + 47014, 47549, 48088, 48630, 49177, 49728, 50283, 50842, 51406, 51973, 52545, 53120, 53700, 54284, 54873, 55465, + 56062, 56663, 57269, 57878, 58492, 59111, 59733, 60360, 60992, 61627, 62268, 62912, 63561, 64215, 64873, 65535}; +#endif + +/** @brief - configuration values for HUB75_I2S driver + * This structure holds configuration vars that are used as + * an initialization values when creating an instance of MatrixPanel_I2S_DMA object. + * All params have it's default values. + */ +struct HUB75_I2S_CFG +{ + + /** + * Enumeration of hardware-specific chips + * used to drive matrix modules + */ + enum shift_driver + { + SHIFTREG = 0, + FM6124, + FM6126A, + ICN2038S, + MBI5124, + SM5266P + }; + + /** + * I2S clock speed selector + */ + enum clk_speed + { + HZ_8M = 8000000, + HZ_10M = 10000000, + HZ_15M = 15000000, + HZ_20M = 20000000 + }; + + // + // Members must be in order of declaration or it breaks Arduino compiling due to strict checking. + // + + // physical width of a single matrix panel module (in pixels, usually it is 64 ;) ) + uint16_t mx_width; + + // physical height of a single matrix panel module (in pixels, usually almost always it is either 32 or 64) + uint16_t mx_height; + + // number of chained panels regardless of the topology, default 1 - a single matrix module + uint16_t chain_length; + + // GPIO Mapping + struct i2s_pins + { + int8_t r1, g1, b1, r2, g2, b2, a, b, c, d, e, lat, oe, clk; + } gpio; + + // Matrix driver chip type - default is a plain shift register + shift_driver driver; + + // use DMA double buffer (twice as much RAM required) + bool double_buff; + + // I2S clock speed + clk_speed i2sspeed; + + // How many clock cycles to blank OE before/after LAT signal change, default is 1 clock + uint8_t latch_blanking; + + /** + * I2S clock phase + * 0 - data lines are clocked with negative edge + * Clk /¯\_/¯\_/ + * LAT __/¯¯¯\__ + * EO ¯¯¯¯¯¯\___ + * + * 1 - data lines are clocked with positive edge (default now as of 10 June 2021) + * https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/130 + * Clk \_/¯\_/¯\ + * LAT __/¯¯¯\__ + * EO ¯¯¯¯¯¯\__ + * + */ + bool clkphase; + + // Minimum refresh / scan rate needs to be configured on start due to LSBMSB_TRANSITION_BIT calculation in allocateDMAmemory() + uint8_t min_refresh_rate; + + // struct constructor + HUB75_I2S_CFG( + uint16_t _w = MATRIX_WIDTH, + uint16_t _h = MATRIX_HEIGHT, + uint16_t _chain = CHAIN_LENGTH, + i2s_pins _pinmap = { + R1_PIN_DEFAULT, G1_PIN_DEFAULT, B1_PIN_DEFAULT, R2_PIN_DEFAULT, G2_PIN_DEFAULT, B2_PIN_DEFAULT, + A_PIN_DEFAULT, B_PIN_DEFAULT, C_PIN_DEFAULT, D_PIN_DEFAULT, E_PIN_DEFAULT, + LAT_PIN_DEFAULT, OE_PIN_DEFAULT, CLK_PIN_DEFAULT}, + shift_driver _drv = SHIFTREG, bool _dbuff = false, clk_speed _i2sspeed = HZ_15M, + uint8_t _latblk = DEFAULT_LAT_BLANKING, // Anything > 1 seems to cause artefacts on ICS panels + bool _clockphase = true, uint16_t _min_refresh_rate = 60, uint8_t _pixel_color_depth_bits = PIXEL_COLOR_DEPTH_BITS_DEFAULT) : mx_width(_w), mx_height(_h), chain_length(_chain), gpio(_pinmap), driver(_drv), double_buff(_dbuff), i2sspeed(_i2sspeed), latch_blanking(_latblk), clkphase(_clockphase), min_refresh_rate(_min_refresh_rate) + { + setPixelColorDepthBits(_pixel_color_depth_bits); + } + + // pixel_color_depth_bits must be between 12 and 2, and mask_offset needs to be calculated accordently + // so they have to be private with getter (and setter) + void setPixelColorDepthBits(uint8_t _pixel_color_depth_bits) + { + if (_pixel_color_depth_bits > PIXEL_COLOR_DEPTH_BITS_MAX || _pixel_color_depth_bits < 2) + { + + if (_pixel_color_depth_bits > PIXEL_COLOR_DEPTH_BITS_MAX) + { + pixel_color_depth_bits = PIXEL_COLOR_DEPTH_BITS_MAX; + } + else + { + pixel_color_depth_bits = 2; + } + ESP_LOGW("HUB75_I2S_CFG", "Invalid pixel_color_depth_bits (%d): 2 <= pixel_color_depth_bits <= %d, choosing nearest valid %d", _pixel_color_depth_bits, PIXEL_COLOR_DEPTH_BITS_MAX, pixel_color_depth_bits); + } + else + { + pixel_color_depth_bits = _pixel_color_depth_bits; + } + } + + uint8_t getPixelColorDepthBits() + { + return pixel_color_depth_bits; + } + +private: + // these were priviously handeld as defines (PIXEL_COLOR_DEPTH_BITS, MASK_OFFSET) + // to make it changable after compilation, it is now part of the config + uint8_t pixel_color_depth_bits; +}; // end of structure HUB75_I2S_CFG + +/***************************************************************************************/ +#ifdef USE_GFX_ROOT +class MatrixPanel_I2S_DMA : public GFX +{ +#elif !defined NO_GFX +class MatrixPanel_I2S_DMA : public Adafruit_GFX +{ +#else +class MatrixPanel_I2S_DMA +{ +#endif + + // ------- PUBLIC ------- +public: + /** + * MatrixPanel_I2S_DMA + * + * default predefined values are used for matrix configuration + * + */ + MatrixPanel_I2S_DMA() +#ifdef USE_GFX_ROOT + : GFX(MATRIX_WIDTH, MATRIX_HEIGHT) +#elif !defined NO_GFX + : Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT) +#endif + { + } + + /** + * MatrixPanel_I2S_DMA + * + * @param {HUB75_I2S_CFG} opts : structure with matrix configuration + * + */ + MatrixPanel_I2S_DMA(const HUB75_I2S_CFG &opts) +#ifdef USE_GFX_ROOT + : GFX(opts.mx_width * opts.chain_length, opts.mx_height) +#elif !defined NO_GFX + : Adafruit_GFX(opts.mx_width * opts.chain_length, opts.mx_height) +#endif + { + setCfg(opts); + } + + /* Propagate the DMA pin configuration, allocate DMA buffs and start data output, initially blank */ + bool begin() + { + + if (initialized) + return true; // we don't do this twice or more! + if (!config_set) + return false; + + ESP_LOGI("begin()", "Using GPIO %d for R1_PIN", m_cfg.gpio.r1); + ESP_LOGI("begin()", "Using GPIO %d for G1_PIN", m_cfg.gpio.g1); + ESP_LOGI("begin()", "Using GPIO %d for B1_PIN", m_cfg.gpio.b1); + ESP_LOGI("begin()", "Using GPIO %d for R2_PIN", m_cfg.gpio.r2); + ESP_LOGI("begin()", "Using GPIO %d for G2_PIN", m_cfg.gpio.g2); + ESP_LOGI("begin()", "Using GPIO %d for B2_PIN", m_cfg.gpio.b2); + ESP_LOGI("begin()", "Using GPIO %d for A_PIN", m_cfg.gpio.a); + ESP_LOGI("begin()", "Using GPIO %d for B_PIN", m_cfg.gpio.b); + ESP_LOGI("begin()", "Using GPIO %d for C_PIN", m_cfg.gpio.c); + ESP_LOGI("begin()", "Using GPIO %d for D_PIN", m_cfg.gpio.d); + ESP_LOGI("begin()", "Using GPIO %d for E_PIN", m_cfg.gpio.e); + ESP_LOGI("begin()", "Using GPIO %d for LAT_PIN", m_cfg.gpio.lat); + ESP_LOGI("begin()", "Using GPIO %d for OE_PIN", m_cfg.gpio.oe); + ESP_LOGI("begin()", "Using GPIO %d for CLK_PIN", m_cfg.gpio.clk); + + // initialize some specific panel drivers + if (m_cfg.driver) + shiftDriver(m_cfg); + +#if defined(SPIRAM_DMA_BUFFER) + // Trick library into dropping colour depth slightly when using PSRAM. + // Actual output clockrate override occurs in configureDMA + m_cfg.i2sspeed = HUB75_I2S_CFG::HZ_8M; +#endif + + /* As DMA buffers are dynamically allocated, we must allocated in begin() + * Ref: https://github.com/espressif/arduino-esp32/issues/831 + */ + if (!allocateDMAmemory()) + { + return false; + } // couldn't even get the basic ram required. + + // Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot. + resetbuffers(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage + + // Setup the ESP32 DMA Engine. Sprite_TM built this stuff. + configureDMA(m_cfg); // DMA and I2S configuration and setup + + // showDMABuffer(); // show backbuf_id of 0 + + if (!initialized) + { + ESP_LOGE("being()", "MatrixPanel_I2S_DMA::begin() failed!"); + } + + return initialized; + } + + // Obj destructor + ~MatrixPanel_I2S_DMA() + { + + dma_bus.release(); + } + + /* + * overload for compatibility + */ + bool begin(int r1, int g1 = G1_PIN_DEFAULT, int b1 = B1_PIN_DEFAULT, int r2 = R2_PIN_DEFAULT, int g2 = G2_PIN_DEFAULT, int b2 = B2_PIN_DEFAULT, int a = A_PIN_DEFAULT, int b = B_PIN_DEFAULT, int c = C_PIN_DEFAULT, int d = D_PIN_DEFAULT, int e = E_PIN_DEFAULT, int lat = LAT_PIN_DEFAULT, int oe = OE_PIN_DEFAULT, int clk = CLK_PIN_DEFAULT); + bool begin(const HUB75_I2S_CFG &cfg); + + // Adafruit's BASIC DRAW API (565 colour format) + virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation + virtual void fillScreen(uint16_t color); // overwrite adafruit implementation + + /** + * A wrapper to fill whatever selected DMA buffer / screen with black + */ + inline void clearScreen() { updateMatrixDMABuffer(0, 0, 0); }; + +#ifndef NO_FAST_FUNCTIONS + /** + * @brief - override Adafruit's FastVLine + * this works faster than multiple consecutive pixel by pixel drawPixel() call + */ + virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) + { + uint8_t r, g, b; + color565to888(color, r, g, b); + + int16_t w = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + + } + // rgb888 overload + virtual inline void drawFastVLine(int16_t x, int16_t y, int16_t h, uint8_t r, uint8_t g, uint8_t b) + { + int16_t w = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + }; + + /** + * @brief - override Adafruit's FastHLine + * this works faster than multiple consecutive pixel by pixel drawPixel() call + */ + virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) + { + uint8_t r, g, b; + color565to888(color, r, g, b); + + int16_t h = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + + } + // rgb888 overload + virtual inline void drawFastHLine(int16_t x, int16_t y, int16_t w, uint8_t r, uint8_t g, uint8_t b) + { + int16_t h = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + }; + + /** + * @brief - override Adafruit's fillRect + * this works much faster than multiple consecutive per-pixel drawPixel() calls + */ + virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) + { + uint8_t r, g, b; + color565to888(color, r, g, b); + + transform(x, y, w, h); + fillRectDMA(x, y, w, h, r, g, b); + + } + // rgb888 overload + virtual inline void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b) + { + + transform(x, y, w, h); + fillRectDMA(x, y, w, h, r, g, b); + + } +#endif + + void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); + void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); + +#ifdef USE_GFX_ROOT + // 24bpp FASTLED CRGB colour struct support + void fillScreen(CRGB color); + void drawPixel(int16_t x, int16_t y, CRGB color); +#endif + + void drawIcon(int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows); + + // Colour 444 is a 4 bit scale, so 0 to 15, colour 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)! + static uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r * 17, g * 17, b * 17); } + + // Converts RGB888 to RGB565 + static uint16_t color565(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! + + // Converts RGB333 to RGB565 + static uint16_t color333(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function. + + /** + * @brief - convert RGB565 to RGB888 + * @param uint16_t colour - RGB565 input colour + * @param uint8_t &r, &g, &b - refs to variables where converted colors would be emplaced + */ + static void color565to888(const uint16_t color, uint8_t &r, uint8_t &g, uint8_t &b); + + inline void flipDMABuffer() + { + if (!m_cfg.double_buff) + { + return; + } + + dma_bus.flip_dma_output_buffer(back_buffer_id); + + back_buffer_id ^= 1; + fb = &frame_buffer[back_buffer_id]; + + + + } + + /** + * @param uint8_t b - 8-bit brightness value + */ + void setBrightness(const uint8_t b) + { + if (!initialized) + { + ESP_LOGI("setBrightness()", "Tried to set output brightness before begin()"); + return; + } + + brightness = b; + brtCtrlOEv2(b, 0); + + if (m_cfg.double_buff) + { + brtCtrlOEv2(b, 1); + } + } + + /** + * @param uint8_t b - 8-bit brightness value + */ + void setPanelBrightness(const uint8_t b) + { + setBrightness(b); + } + + /** + * this is just a wrapper to control brightness + * with an 8-bit value (0-255), very popular in FastLED-based sketches :) + * @param uint8_t b - 8-bit brightness value + */ + void setBrightness8(const uint8_t b) + { + setBrightness(b); + // setPanelBrightness(b * PIXELS_PER_ROW / 256); + } + + /** + * @brief - Sets how many clock cycles to blank OE before/after LAT signal change + * @param uint8_t pulses - clocks before/after OE + * default is DEFAULT_LAT_BLANKING + * Max is MAX_LAT_BLANKING + * @returns - new value for m_cfg.latch_blanking + */ + uint8_t setLatBlanking(uint8_t pulses); + + /** + * Get a class configuration struct + * + */ + const HUB75_I2S_CFG &getCfg() const { return m_cfg; }; + + inline bool setCfg(const HUB75_I2S_CFG &cfg) + { + if (initialized) + return false; + + m_cfg = cfg; + PIXELS_PER_ROW = m_cfg.mx_width * m_cfg.chain_length; + ROWS_PER_FRAME = m_cfg.mx_height / MATRIX_ROWS_IN_PARALLEL; + MASK_OFFSET = 16 - m_cfg.getPixelColorDepthBits(); + + config_set = true; + return true; + } + + /** + * Stop the ESP32 DMA Engine. Screen will forever be black until next ESP reboot. + */ + void stopDMAoutput() + { + resetbuffers(); + // i2s_parallel_stop_dma(ESP32_I2S_DEVICE); + dma_bus.dma_transfer_stop(); + } + + // ------- PROTECTED ------- + // those might be useful for child classes, like VirtualMatrixPanel +protected: + /** + * @brief - clears and reinitializes colour/control data in DMA buffs + * When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits. + * Those control bits are constants during the entire DMA sweep and never changed when updating just pixel colour data + * so we could set it once on DMA buffs initialization and forget. + * This effectively clears buffers to blank BLACK and makes it ready to display output. + * (Brightness control via OE bit manipulation is another case) + */ + void clearFrameBuffer(bool _buff_id); + + /* Update a specific pixel in the DMA buffer to a colour */ + void updateMatrixDMABuffer(uint16_t x, uint16_t y, uint8_t red, uint8_t green, uint8_t blue); + + /* Update the entire DMA buffer (aka. The RGB Panel) a certain colour (wipe the screen basically) */ + void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue); + + /** + * wipes DMA buffer(s) and reset all colour/service bits + */ + inline void resetbuffers() + { + clearFrameBuffer(0); + brtCtrlOEv2(brightness, 0); + + if (m_cfg.double_buff) { + + clearFrameBuffer(1); + brtCtrlOEv2(brightness, 1); + + } + } + +#ifndef NO_FAST_FUNCTIONS + /** + * @brief - update DMA buff drawing horizontal line at specified coordinates + * @param x_ccord - line start coordinate x + * @param y_ccord - line start coordinate y + * @param l - line length + * @param r,g,b, - RGB888 colour + */ + void hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue); + + /** + * @brief - update DMA buff drawing horizontal line at specified coordinates + * @param x_ccord - line start coordinate x + * @param y_ccord - line start coordinate y + * @param l - line length + * @param r,g,b, - RGB888 colour + */ + void vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue); + + /** + * @brief - update DMA buff drawing a rectangular at specified coordinates + * uses Fast H/V line draw internally, works faster than multiple consecutive pixel by pixel calls to updateMatrixDMABuffer() + * @param int16_t x, int16_t y - coordinates of a top-left corner + * @param int16_t w, int16_t h - width and height of a rectangular, min is 1 px + * @param uint8_t r - RGB888 colour + * @param uint8_t g - RGB888 colour + * @param uint8_t b - RGB888 colour + */ + void fillRectDMA(int16_t x_coord, int16_t y_coord, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b); +#endif + + // ------- PRIVATE ------- +private: + /* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */ + bool allocateDMAmemory(); + + /* Setup the DMA Link List chain and initiate the ESP32 DMA engine */ + void configureDMA(const HUB75_I2S_CFG &opts); + + /** + * pre-init procedures for specific drivers + * + */ + void shiftDriver(const HUB75_I2S_CFG &opts); + + /** + * @brief - FM6124-family chips initialization routine + */ + void fm6124init(const HUB75_I2S_CFG &_cfg); + + /** + * @brief - reset OE bits in DMA buffer in a way to control brightness + * @param brt - brightness level from 0 to row_width + * @param _buff_id - buffer id to control + */ + // void brtCtrlOE(int brt, const bool _buff_id=0); + + /** + * @brief - reset OE bits in DMA buffer in a way to control brightness + * @param brt - brightness level from 0 to row_width + * @param _buff_id - buffer id to control + */ + void brtCtrlOEv2(uint8_t brt, const int _buff_id = 0); + + /** + * @brief - transforms coordinates according to orientation + * @param x - x position origin + * @param y - y position origin + * @param w - rectangular width + * @param h - rectangular height + */ + void transform(int16_t &x, int16_t &y, int16_t &w, int16_t &h) + { +#ifndef NO_GFX + int16_t t; + switch (rotation) + { + case 1: + t = _height - 1 - y - (h - 1); + y = x; + x = t; + t = h; + h = w; + w = t; + return; + case 2: + x = _width - 1 - x - (w - 1); + y = _height - 1 - y - (h - 1); + return; + case 3: + t = y; + y = _width - 1 - x - (w - 1); + x = t; + t = h; + h = w; + w = t; + return; + } +#endif + }; + +public: + /** + * Contains the resulting refresh rate (scan rate) that will be achieved + * based on the i2sspeed, colour depth and min_refresh_rate requested. + */ + int calculated_refresh_rate = 0; + +protected: + Bus_Parallel16 dma_bus; + +private: + + // Matrix i2s settings + HUB75_I2S_CFG m_cfg; + + /* Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel + * (two rows of pixels are refreshed in parallel) + * Memory is allocated (malloc'd) by the row, and not in one massive chunk, for flexibility. + * The whole DMA framebuffer is just a vector of pointers to structs with ESP32_I2S_DMA_STORAGE_TYPE arrays + * Since it's dimensions is unknown prior to class initialization, we just declare it here as empty struct and will do all allocations later. + * Refer to rowBitStruct to get the idea of it's internal structure + */ + frameStruct frame_buffer[2]; + frameStruct *fb; // What framebuffer we are writing pixel changes to? (pointer to either frame_buffer[0] or frame_buffer[1] basically ) used within updateMatrixDMABuffer(...) + + volatile int back_buffer_id = 0; // If using double buffer, which one is NOT active (ie. being displayed) to write too? + int brightness = 128; // If you get ghosting... reduce brightness level. ((60/64)*255) seems to be the limit before ghosting on a 64 pixel wide physical panel for some panels. + int lsbMsbTransitionBit = 0; // For colour depth calculations + + /* ESP32-HUB75-MatrixPanel-I2S-DMA functioning constants + * we should not those once object instance initialized it's DMA structs + * they weree const, but this lead to bugs, when the default constructor was called. + * So now they could be changed, but shouldn't. Maybe put a cpp lock around it, so it can't be changed after initialisation + */ + uint16_t PIXELS_PER_ROW = m_cfg.mx_width * m_cfg.chain_length; // number of pixels in a single row of all chained matrix modules (WIDTH of a combined matrix chain) + uint8_t ROWS_PER_FRAME = m_cfg.mx_height / MATRIX_ROWS_IN_PARALLEL; // RPF - rows per frame, either 16 or 32 depending on matrix module + uint8_t MASK_OFFSET = 16 - m_cfg.getPixelColorDepthBits(); + + // Other private variables + bool initialized = false; + bool config_set = false; + +}; // end Class header + +/***************************************************************************************/ +// https://stackoverflow.com/questions/5057021/why-are-c-inline-functions-in-the-header +/* 2. functions declared in the header must be marked inline because otherwise, every translation unit which includes the header will contain a definition of the function, and the linker will complain about multiple definitions (a violation of the One Definition Rule). The inline keyword suppresses this, allowing multiple translation units to contain (identical) definitions. */ + +/** + * @brief - convert RGB565 to RGB888 + * @param uint16_t colour - RGB565 input colour + * @param uint8_t &r, &g, &b - refs to variables where converted colours would be emplaced + */ +inline void MatrixPanel_I2S_DMA::color565to888(const uint16_t color, uint8_t &r, uint8_t &g, uint8_t &b) +{ + r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6; + g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6; + b = (((color & 0x1F) * 527) + 23) >> 6; +} + +inline void MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color) // adafruit virtual void override +{ + uint8_t r, g, b; + color565to888(color, r, g, b); + + int16_t w = 1, h = 1; + transform(x, y, w, h); + updateMatrixDMABuffer(x, y, r, g, b); +} + +inline void MatrixPanel_I2S_DMA::fillScreen(uint16_t color) // adafruit virtual void override +{ + uint8_t r, g, b; + color565to888(color, r, g, b); + + updateMatrixDMABuffer(r, g, b); // RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer' +} + +inline void MatrixPanel_I2S_DMA::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) +{ + int16_t w = 1, h = 1; + transform(x, y, w, h); + updateMatrixDMABuffer(x, y, r, g, b); +} + +inline void MatrixPanel_I2S_DMA::fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b) +{ + updateMatrixDMABuffer(r, g, b); // RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer' +} + +#ifdef USE_GFX_ROOT +// Support for CRGB values provided via FastLED +inline void MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, CRGB color) +{ + int16_t w = 1, h = 1; + transform(x, y, w, h); + updateMatrixDMABuffer(x, y, color.red, color.green, color.blue); +} + +inline void MatrixPanel_I2S_DMA::fillScreen(CRGB color) +{ + updateMatrixDMABuffer(color.red, color.green, color.blue); +} +#endif + +// Pass 8-bit (each) R,G,B, get back 16-bit packed colour +// https://github.com/squix78/ILI9341Buffer/blob/master/ILI9341_SPI.cpp +inline uint16_t MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b) +{ + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); +} + +// Promote 3/3/3 RGB to Adafruit_GFX 5/6/5 RRRrrGGGgggBBBbb +inline uint16_t MatrixPanel_I2S_DMA::color333(uint8_t r, uint8_t g, uint8_t b) +{ + return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1); +} + +inline void MatrixPanel_I2S_DMA::drawIcon(int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows) +{ + /* drawIcon draws a C style bitmap. + // Example 10x5px bitmap of a yellow sun + // + int half_sun [50] = { + 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, + 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, + 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, + 0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0, + 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, + }; + + MatrixPanel_I2S_DMA matrix; + + matrix.drawIcon (half_sun, 0,0,10,5); + */ + + int i, j; + for (i = 0; i < rows; i++) + { + for (j = 0; j < cols; j++) + { + drawPixel(x + j, y + i, (uint16_t)ico[i * cols + j]); + } + } +} + +#endif + +// Credits: Louis Beaudoin +// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256 + +/* + + This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed + continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable + input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate + RGB pixel input, the rest of the inputs are shared. + + Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins + to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel, + giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high, + the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just + clocked in. + + The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data + will immediately appear on this line. Note: While clocking in data for a line, the *previous* line is still displayed, and these lines should + be set to the value to reflect the position the *previous* line is supposed to be on. + + Finally, the screen has an OE input, which is used to disable the LEDs when latching new data and changing the state of the line select inputs: + doing so hides any artefacts that appear at this time. The OE line is also used to dim the display by only turning it on for a limited time every + line. + + All in all, an image can be displayed by 'scanning' the display, say, 100 times per second. The slowness of the human eye hides the fact that + only one line is showed at a time, and the display looks like every pixel is driven at the same time. + + Now, the RGB inputs for these types of displays are digital, meaning each red, green and blue subpixel can only be on or off. This leads to a + colour palette of 8 pixels, not enough to display nice pictures. To get around this, we use binary code modulation. + + Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without + binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length. + + We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a + normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7 + to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set, + we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0. + + Now, in order to (on average within a frame) turn a LED on for the time specified in the pixel value in the input data, we need to weigh the + subframes. We have 15 pixels: if we show subframe 3 for 8 of them, subframe 2 for 4 of them, subframe 1 for 2 of them and subframe 1 for 1 of + them, this 'automatically' happens. (We also distribute the subframes evenly over the ticks, which reduces flicker.) + + In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory + contains a sequence of all the signals (2xRGB, line select, latch enable, output enable) that need to be sent to the display for that subframe. + Then we ask the I2S-parallel driver to set up a DMA chain so the subframes are sent out in a sequence that satisfies the requirement that + subframe x has to be sent out for (2^x) ticks. Finally, we fill the subframes with image data. + + We use a front buffer/back buffer technique here to make sure the display is refreshed in one go and drawing artefacts do not reach the display. + In practice, for small displays this is not really necessarily. + +*/ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-leddrivers.cpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-leddrivers.cpp new file mode 100644 index 0000000..0bb20f7 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-HUB75-MatrixPanel-leddrivers.cpp @@ -0,0 +1,100 @@ +/* + Various LED Driver chips might need some specific code for initialisation/control logic + +*/ + +#ifdef ARDUINO_ARCH_ESP32 + #include +#else + #include + #define LOW 0 + #define HIGH 1 +#endif +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" + +#define CLK_PULSE gpio_set_level((gpio_num_t) _cfg.gpio.clk, HIGH); gpio_set_level((gpio_num_t) _cfg.gpio.clk, LOW); + +/** + * @brief - pre-init procedures for specific led-drivers + * this method is called before DMA/I2S setup while GPIOs + * aint yet assigned for DMA operation + * + */ +void MatrixPanel_I2S_DMA::shiftDriver(const HUB75_I2S_CFG& _cfg){ + switch (_cfg.driver){ + case HUB75_I2S_CFG::ICN2038S: + case HUB75_I2S_CFG::FM6124: + case HUB75_I2S_CFG::FM6126A: + fm6124init(_cfg); + break; + case HUB75_I2S_CFG::MBI5124: + /* MBI5124 chips must be clocked with positive-edge, since it's LAT signal + * resets on clock's rising edge while high + * https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/files/5952216/5a542453754da.pdf + */ + m_cfg.clkphase=true; + break; + case HUB75_I2S_CFG::SHIFTREG: + default: + break; + } +} + + +void MatrixPanel_I2S_DMA::fm6124init(const HUB75_I2S_CFG& _cfg) { + + ESP_LOGI("LEDdrivers", "MatrixPanel_I2S_DMA - initializing FM6124 driver..."); + + bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; // this sets global matrix brightness power + bool REG2[16] = {0,0,0,0,0, 0,0,0,0,1,0, 0,0,0,0,0}; // a single bit enables the matrix output + + for (uint8_t _pin:{_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2, _cfg.gpio.clk, _cfg.gpio.lat, _cfg.gpio.oe}){ + gpio_set_direction((gpio_num_t) _pin, GPIO_MODE_OUTPUT); + gpio_set_level((gpio_num_t) _pin, LOW); + } + + gpio_set_level((gpio_num_t) _cfg.gpio.oe, HIGH); // Disable Display + + // Send Data to control register REG1 + // this sets the matrix brightness actually + for (int l = 0; l < PIXELS_PER_ROW; l++){ + for (uint8_t _pin:{_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2}) + gpio_set_level((gpio_num_t) _pin, REG1[l%16]); // we have 16 bits shifters and write the same value all over the matrix array + + if (l > PIXELS_PER_ROW - 12){ // pull the latch 11 clocks before the end of matrix so that REG1 starts counting to save the value + gpio_set_level((gpio_num_t) _cfg.gpio.lat, HIGH); + } + CLK_PULSE + } + + // drop the latch and save data to the REG1 all over the FM6124 chips + gpio_set_level((gpio_num_t) _cfg.gpio.lat, LOW); + + // Send Data to control register REG2 (enable LED output) + for (int l = 0; l < PIXELS_PER_ROW; l++){ + for (uint8_t _pin:{_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2}) + gpio_set_level((gpio_num_t) _pin, REG2[l%16]); // we have 16 bits shifters and we write the same value all over the matrix array + + if (l > PIXELS_PER_ROW - 13){ // pull the latch 12 clocks before the end of matrix so that reg2 stars counting to save the value + gpio_set_level((gpio_num_t) _cfg.gpio.lat, HIGH); + } + CLK_PULSE + } + + // drop the latch and save data to the REG1 all over the FM6126 chips + gpio_set_level((gpio_num_t) _cfg.gpio.lat, LOW); + + // blank data regs to keep matrix clear after manipulations + for (uint8_t _pin:{_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2}) + gpio_set_level((gpio_num_t) _pin, LOW); + + for (int l = 0; l < PIXELS_PER_ROW; ++l){ + CLK_PULSE + } + + gpio_set_level((gpio_num_t) _cfg.gpio.lat, HIGH); + CLK_PULSE + gpio_set_level((gpio_num_t) _cfg.gpio.lat, LOW); + gpio_set_level((gpio_num_t) _cfg.gpio.oe, LOW); // Enable Display + CLK_PULSE +} \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-VirtualMatrixPanel-I2S-DMA.h b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-VirtualMatrixPanel-I2S-DMA.h new file mode 100644 index 0000000..ebac204 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/ESP32-VirtualMatrixPanel-I2S-DMA.h @@ -0,0 +1,554 @@ +#ifndef _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA +#define _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA + +/******************************************************************* + Class contributed by Brian Lough, and expanded by Faptastic. + + Originally designed to allow CHAINING of panels together to create + a 'bigger' display of panels. i.e. Chaining 4 panels into a 2x2 + grid. + + However, the function of this class has expanded now to also manage + the output for + + 1) TWO scan panels = Two rows updated in parallel. + * 64px high panel = sometimes referred to as 1/32 scan + * 32px high panel = sometimes referred to as 1/16 scan + * 16px high panel = sometimes referred to as 1/8 scan + + 2) FOUR scan panels = Four rows updated in parallel + * 32px high panel = sometimes referred to as 1/8 scan + * 16px high panel = sometimes referred to as 1/4 scan + + YouTube: https://www.youtube.com/brianlough + Tindie: https://www.tindie.com/stores/brianlough/ + Twitter: https://twitter.com/witnessmenow + *******************************************************************/ + +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" +#ifndef NO_GFX +#include +#endif + +// #include + +struct VirtualCoords +{ + int16_t x; + int16_t y; + int16_t virt_row; // chain of panels row + int16_t virt_col; // chain of panels col + + VirtualCoords() : x(0), y(0) + { + } +}; + +enum PANEL_SCAN_RATE +{ + NORMAL_TWO_SCAN, + NORMAL_ONE_SIXTEEN, // treated as the same + FOUR_SCAN_32PX_HIGH, + FOUR_SCAN_16PX_HIGH, + FOUR_SCAN_64PX_HIGH +}; + +// Chaining approach... From the perspective of the DISPLAY / LED side of the chain of panels. +enum PANEL_CHAIN_TYPE +{ + CHAIN_NONE, + CHAIN_TOP_LEFT_DOWN, + CHAIN_TOP_RIGHT_DOWN, + CHAIN_BOTTOM_LEFT_UP, + CHAIN_BOTTOM_RIGHT_UP, + CHAIN_TOP_LEFT_DOWN_ZZ, /// ZigZag chaining. Might need a big ass cable to do this, all panels right way up. + CHAIN_TOP_RIGHT_DOWN_ZZ, + CHAIN_BOTTOM_RIGHT_UP_ZZ, + CHAIN_BOTTOM_LEFT_UP_ZZ +}; + +#ifdef USE_GFX_ROOT +class VirtualMatrixPanel : public GFX +#elif !defined NO_GFX +class VirtualMatrixPanel : public Adafruit_GFX +#else +class VirtualMatrixPanel +#endif +{ + +public: + VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, PANEL_CHAIN_TYPE _panel_chain_type = CHAIN_NONE) +#ifdef USE_GFX_ROOT + : GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY) +#elif !defined NO_GFX + : Adafruit_GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY) +#endif + { + this->display = &disp; + + panel_chain_type = _panel_chain_type; + + panelResX = _panelResX; + panelResY = _panelResY; + + vmodule_rows = _vmodule_rows; + vmodule_cols = _vmodule_cols; + + virtualResX = vmodule_cols * _panelResX; + virtualResY = vmodule_rows * _panelResY; + + dmaResX = panelResX * vmodule_rows * vmodule_cols - 1; + + /* Virtual Display width() and height() will return a real-world value. For example: + * Virtual Display width: 128 + * Virtual Display height: 64 + * + * So, not values that at 0 to X-1 + */ + + coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + } + + // equivalent methods of the matrix library so it can be just swapped out. + void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation + void fillScreen(uint16_t color); // overwrite adafruit implementation + void setRotation(int rotate); // overwrite adafruit implementation + + void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); + void clearScreen() { display->clearScreen(); } + void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); + +#ifdef USE_GFX_ROOT + // 24bpp FASTLED CRGB colour struct support + void fillScreen(CRGB color); + void drawPixel(int16_t x, int16_t y, CRGB color); +#endif + + uint16_t color444(uint8_t r, uint8_t g, uint8_t b) + { + return display->color444(r, g, b); + } + uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { return display->color565(r, g, b); } + uint16_t color333(uint8_t r, uint8_t g, uint8_t b) { return display->color333(r, g, b); } + + void flipDMABuffer() { display->flipDMABuffer(); } + void drawDisplayTest(); + + void setPhysicalPanelScanRate(PANEL_SCAN_RATE rate); + void setZoomFactor(int scale); + +private: + MatrixPanel_I2S_DMA *display; + + PANEL_CHAIN_TYPE panel_chain_type; + PANEL_SCAN_RATE panel_scan_rate = NORMAL_TWO_SCAN; + + virtual VirtualCoords getCoords(int16_t x, int16_t y); + VirtualCoords coords; + + int16_t virtualResX; + int16_t virtualResY; + + int16_t _virtualResX; ///< Display width as modified by current rotation + int16_t _virtualResY; ///< Display height as modified by current rotation + + int16_t vmodule_rows; + int16_t vmodule_cols; + + int16_t panelResX; + int16_t panelResY; + + int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it) + + int _rotate = 0; + + int _scale_factor = 0; + +}; // end Class header + +/** + * Calculate virtual->real co-ordinate mapping to underlying single chain of panels connected to ESP32. + * Updates the private class member variable 'coords', so no need to use the return value. + * Not thread safe, but not a concern for ESP32 sketch anyway... I think. + */ +inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t virt_x, int16_t virt_y) +{ + +#if !defined NO_GFX + // I don't give any support if Adafruit GFX isn't being used. + + if (virt_x < 0 || virt_x >= _width || virt_y < 0 || virt_y >= _height) // _width and _height are defined in the adafruit constructor + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + return coords; + } +#else + + if (virt_x < 0 || virt_x >= _virtualResX || virt_y < 0 || virt_y >= _virtualResY) // _width and _height are defined in the adafruit constructor + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + return coords; + } + +#endif + + // Do we want to rotate? + switch (_rotate) { + case 0: //no rotation, do nothing + break; + + case (1): //90 degree rotation + { + int16_t temp_x = virt_x; + virt_x = virt_y; + virt_y = virtualResY - 1 - temp_x; + break; + } + + case (2): //180 rotation + { + virt_x = virtualResX - 1 - virt_x; + virt_y = virtualResY - 1 - virt_y; + break; + } + + case (3): //270 rotation + { + int16_t temp_x = virt_x; + virt_x = virtualResX - 1 - virt_y; + virt_y = temp_x; + break; + } + } + + int row = (virt_y / panelResY); // 0 indexed + switch (panel_chain_type) + { + case (CHAIN_TOP_RIGHT_DOWN): + { + if ((row % 2) == 1) + { // upside down panel + + // Serial.printf("Condition 1, row %d ", row); + + // reversed for the row + coords.x = dmaResX - virt_x - (row * virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + } + else + { + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + } + break; + + case (CHAIN_TOP_RIGHT_DOWN_ZZ): + { + // Right side up. Starting from top right all the way down. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + case (CHAIN_TOP_LEFT_DOWN): // OK -> modulus opposite of CHAIN_TOP_RIGHT_DOWN + { + if ((row % 2) == 0) + { // reversed panel + + // Serial.printf("Condition 1, row %d ", row); + coords.x = dmaResX - virt_x - (row * virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + } + else + { + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + } + break; + + case (CHAIN_TOP_LEFT_DOWN_ZZ): + { + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + case (CHAIN_BOTTOM_LEFT_UP): // + { + row = vmodule_rows - row - 1; + + if ((row % 2) == 1) + { + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row * virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + } + break; + + case (CHAIN_BOTTOM_LEFT_UP_ZZ): // + { + row = vmodule_rows - row - 1; + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP): // OK -> modulus opposite of CHAIN_BOTTOM_LEFT_UP + { + row = vmodule_rows - row - 1; + + if ((row % 2) == 0) + { // right side up + + // Serial.printf("Condition 1, row %d ", row); + // refersed for the row + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row * virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP_ZZ): + { + // Right side up. Starting bottom right all the way up. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + row = vmodule_rows - row - 1; + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + // Q: 1 row!? Why? + // A: In cases people are only using virtual matrix panel for panels of non-standard scan rates. + default: + coords.x = virt_x; coords.y = virt_y; + break; + + } // end switch + + + /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the + * the underlying hardware library is designed for (because + * there's only 2 x RGB pins... and convert this to 1/4 or something + */ + + if ((panel_scan_rate == FOUR_SCAN_32PX_HIGH) || (panel_scan_rate == FOUR_SCAN_64PX_HIGH)) + { + + if (panel_scan_rate == FOUR_SCAN_64PX_HIGH) + { + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/345#issuecomment-1510401192 + if ((virt_y & 8) != ((virt_y & 16) >> 1)) { virt_y = (virt_y & 0b11000) ^ 0b11000 + (virt_y & 0b11100111); } + } + + + /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at + on the panel or chain of panels, per the chaining configuration) to a 1/8 panels + double 'stretched' and 'squished' coordinates which is what needs to be sent from the + DMA buffer. + + Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup + as if the panel is 2 * W and 0.5 * H ! + */ + + if ((virt_y & 8) == 0) + { + coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + // http://cpp.sh/4ak5u + // Real number of DMA y rows is half reality + // coords.y = (y / 16)*8 + (y & 0b00000111); + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + } + else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH) + { + if ((virt_y & 8) == 0) + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + if (virt_y < 32) + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + else + { + coords.y = ((virt_y - 32) >> 4) * 8 + (virt_y & 0b00000111); + coords.x += 256; + } + } + + return coords; +} + +inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color) +{ // adafruit virtual void override + + if (_scale_factor > 1) // only from 2 and beyond + { + int16_t scaled_x_start_pos = x * _scale_factor; + int16_t scaled_y_start_pos = y * _scale_factor; + + for (int16_t x = 0; x < _scale_factor; x++) { + for (int16_t y = 0; y < _scale_factor; y++) { + VirtualCoords result = this->getCoords(scaled_x_start_pos+x, scaled_y_start_pos+y); + // Serial.printf("Requested virtual x,y coord (%d, %d), got phyical chain coord of (%d,%d)\n", x,y, coords.x, coords.y); + this->display->drawPixel(result.x, result.y, color); + } + } + } + else + { + this->getCoords(x, y); + // Serial.printf("Requested virtual x,y coord (%d, %d), got phyical chain coord of (%d,%d)\n", x,y, coords.x, coords.y); + this->display->drawPixel(coords.x, coords.y, color); + } +} + +inline void VirtualMatrixPanel::fillScreen(uint16_t color) +{ // adafruit virtual void override + this->display->fillScreen(color); +} + +inline void VirtualMatrixPanel::fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b) +{ + this->display->fillScreenRGB888(r, g, b); +} + +inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) +{ + this->getCoords(x, y); + this->display->drawPixelRGB888(coords.x, coords.y, r, g, b); +} + +#ifdef USE_GFX_ROOT +// Support for CRGB values provided via FastLED +inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, CRGB color) +{ + this->getCoords(x, y); + this->display->drawPixel(coords.x, coords.y, color); +} + +inline void VirtualMatrixPanel::fillScreen(CRGB color) +{ + this->display->fillScreen(color); +} +#endif + +inline void VirtualMatrixPanel::setRotation(int rotate) +{ + if(rotate < 4 && rotate >= 0) + _rotate = rotate; + + // Change the _width and _height variables used by the underlying adafruit gfx library. + // Actual pixel rotation / mapping is done in the getCoords function. + rotation = (rotate & 3); + switch (rotation) { + case 0: // nothing + case 2: // 180 + _virtualResX = virtualResX; + _virtualResY = virtualResY; + +#if !defined NO_GFX + _width = virtualResX; // adafruit base class + _height = virtualResY; // adafruit base class +#endif + break; + case 1: + case 3: + _virtualResX = virtualResY; + _virtualResY = virtualResX; + +#if !defined NO_GFX + _width = virtualResY; // adafruit base class + _height = virtualResX; // adafruit base class +#endif + break; + } + + +} + +inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate) +{ + panel_scan_rate = rate; +} + +inline void VirtualMatrixPanel::setZoomFactor(int scale) +{ + if(scale < 5 && scale > 0) + _scale_factor = scale; + +} + +#ifndef NO_GFX +inline void VirtualMatrixPanel::drawDisplayTest() +{ + // Write to the underlying panels only via the dma_display instance. + this->display->setFont(&FreeSansBold12pt7b); + this->display->setTextColor(this->display->color565(255, 255, 0)); + this->display->setTextSize(1); + + for (int panel = 0; panel < vmodule_cols * vmodule_rows; panel++) + { + int top_left_x = (panel == 0) ? 0 : (panel * panelResX); + this->display->drawRect(top_left_x, 0, panelResX, panelResY, this->display->color565(0, 255, 0)); + this->display->setCursor((panel * panelResX) + 2, panelResY - 4); + this->display->print((vmodule_cols * vmodule_rows) - panel); + } +} +#endif + +/* +// need to recreate this one, as it wouldn't work to just map where it starts. +inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t icon_cols, int16_t icon_rows) { + int i, j; + for (i = 0; i < icon_rows; i++) { + for (j = 0; j < icon_cols; j++) { + // This is a call to this libraries version of drawPixel + // which will map each pixel, which is what we want. + //drawPixelRGB565 (x + j, y + i, ico[i * module_cols + j]); + drawPixel (x + j, y + i, ico[i * icon_cols + j]); + } + } +} +*/ + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/RGB_HUB75_PINS.png b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/RGB_HUB75_PINS.png new file mode 100644 index 0000000..2dc609e Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/RGB_HUB75_PINS.png differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32-default-pins.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32-default-pins.hpp new file mode 100644 index 0000000..5ee94b9 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32-default-pins.hpp @@ -0,0 +1,18 @@ +#pragma once + +#define R1_PIN_DEFAULT 25 +#define G1_PIN_DEFAULT 26 +#define B1_PIN_DEFAULT 27 +#define R2_PIN_DEFAULT 14 +#define G2_PIN_DEFAULT 12 +#define B2_PIN_DEFAULT 13 + +#define A_PIN_DEFAULT 23 +#define B_PIN_DEFAULT 19 +#define C_PIN_DEFAULT 5 +#define D_PIN_DEFAULT 17 +#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel. + +#define LAT_PIN_DEFAULT 4 +#define OE_PIN_DEFAULT 15 +#define CLK_PIN_DEFAULT 16 diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32_i2s_parallel_dma.cpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32_i2s_parallel_dma.cpp new file mode 100644 index 0000000..5511d6f --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32_i2s_parallel_dma.cpp @@ -0,0 +1,597 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) + +Modified heavily for the ESP32 HUB75 DMA library by: + [mrfaptastic](https://github.com/mrfaptastic) + +/----------------------------------------------------------------------------*/ +#include +#if defined (CONFIG_IDF_TARGET_ESP32) || defined (CONFIG_IDF_TARGET_ESP32S2) + +#include "esp32_i2s_parallel_dma.hpp" + +#include +#include +#include + +#include // Need to make sure this is uncommented to get ESP_LOG output on (Arduino) Serial output!!!! +#include +#include + +// Get CPU freq function. +#include + + + volatile bool previousBufferFree = true; + + static void IRAM_ATTR i2s_isr(void* arg) { + + // From original Sprite_TM Code + //REG_WRITE(I2S_INT_CLR_REG(1), (REG_READ(I2S_INT_RAW_REG(1)) & 0xffffffc0) | 0x3f); + + // Clear flag so we can get retriggered + SET_PERI_REG_BITS(I2S_INT_CLR_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S); + + // at this point, the previously active buffer is free, go ahead and write to it + previousBufferFree = true; + } + + bool DRAM_ATTR i2s_parallel_is_previous_buffer_free() { + return previousBufferFree; + } + + + // Static + i2s_dev_t* getDev() + { + #if defined (CONFIG_IDF_TARGET_ESP32S2) + return &I2S0; + #else + return (ESP32_I2S_DEVICE == 0) ? &I2S0 : &I2S1; + #endif + + } + + // Static + void _gpio_pin_init(int pin) + { + if (pin >= 0) + { + gpio_pad_select_gpio(pin); + //gpio_hi(pin); + gpio_set_direction((gpio_num_t)pin, GPIO_MODE_OUTPUT); + gpio_set_drive_capability((gpio_num_t)pin, (gpio_drive_cap_t)3); // esp32s3 as well? + } + } + + inline int i2s_parallel_get_memory_width(int port, int width) { + switch(width) { + case 8: + + #if !defined (CONFIG_IDF_TARGET_ESP32S2) + + // Only I2S1 on the legacy ESP32 WROOM MCU supports space saving single byte 8 bit parallel access + if(port == 1) + { + return 1; + } else { + return 2; + } + #else + return 1; + #endif + + case 16: + return 2; + case 24: + return 4; + default: + return -ESP_ERR_INVALID_ARG; + } + } + + + void Bus_Parallel16::config(const config_t& cfg) + { + ESP_LOGI("ESP32/S2", "Performing config for ESP32 or ESP32-S2"); + _cfg = cfg; + _dev = getDev(); + } + + bool Bus_Parallel16::init(void) // The big one that gets everything setup. + { + ESP_LOGI("ESP32/S2", "Performing DMA bus init() for ESP32 or ESP32-S2"); + + if(_cfg.parallel_width < 8 || _cfg.parallel_width >= 24) { + return false; + } + + auto dev = _dev; + volatile int iomux_signal_base; + volatile int iomux_clock; + int irq_source; + + // Initialize I2S0 peripheral + if (ESP32_I2S_DEVICE == I2S_NUM_0) + { + periph_module_reset(PERIPH_I2S0_MODULE); + periph_module_enable(PERIPH_I2S0_MODULE); + + iomux_clock = I2S0O_WS_OUT_IDX; + irq_source = ETS_I2S0_INTR_SOURCE; + + switch(_cfg.parallel_width) { + case 8: + case 16: + iomux_signal_base = I2S0O_DATA_OUT8_IDX; + break; + case 24: + iomux_signal_base = I2S0O_DATA_OUT0_IDX; + break; + default: + return ESP_ERR_INVALID_ARG; + } + } + + #if !defined (CONFIG_IDF_TARGET_ESP32S2) + // Can't compile if I2S1 if it doesn't exist with that hardware's IDF.... + else { + periph_module_reset(PERIPH_I2S1_MODULE); + periph_module_enable(PERIPH_I2S1_MODULE); + iomux_clock = I2S1O_WS_OUT_IDX; + irq_source = ETS_I2S1_INTR_SOURCE; + + switch(_cfg.parallel_width) { + case 16: + iomux_signal_base = I2S1O_DATA_OUT8_IDX; + break; + case 8: + case 24: + iomux_signal_base = I2S1O_DATA_OUT0_IDX; + break; + default: + return ESP_ERR_INVALID_ARG; + } + } + #endif + + // Setup GPIOs + int bus_width = _cfg.parallel_width; + + // Clock output GPIO setup + _gpio_pin_init(_cfg.pin_rd); // not used + _gpio_pin_init(_cfg.pin_wr); // clock + _gpio_pin_init(_cfg.pin_rs); // not used + + // Data output GPIO setup + int8_t* pins = _cfg.pin_data; + + for(int i = 0; i < bus_width; i++) + _gpio_pin_init(pins[i]); + + // Route clock signal to clock pin + gpio_matrix_out(_cfg.pin_wr, iomux_clock, _cfg.invert_pclk, 0); // inverst clock if required + + for (size_t i = 0; i < bus_width; i++) { + + if (pins[i] >= 0) { + gpio_matrix_out(pins[i], iomux_signal_base + i, false, false); + } + } + + ////////////////////////////// Clock configuration ////////////////////////////// + + auto freq = (_cfg.bus_freq); + ESP_LOGD("ESP32/S2", "Requested output clock frequency: %d Mhz", (freq/1000000)); + + // What is the current CPU frequency? + + // Calculate clock divider for ESP32-S2 + #if defined (CONFIG_IDF_TARGET_ESP32S2) + + // Right shift (>> 1) and divide 160mhz in half to 80Mhz for the calc due to the fact + // that later we must have tx_bck_div_num = 2 for both esp32 and esp32-s2 + + //static uint32_t pll_160M_clock_d2 = 160 * 1000 * 1000 >> 1; + + // I2S_CLKM_DIV_NUM 2=40MHz / 3=27MHz / 4=20MHz / 5=16MHz / 8=10MHz / 10=8MHz + //auto _div_num = std::min(255u, 1 + ((pll_160M_clock_d2) / (1 + freq))); + auto _div_num = 160000000L / freq / i2s_parallel_get_memory_width(ESP32_I2S_DEVICE, 16); // 16 bits in parallel + + if(_div_num < 2 || _div_num > 0xFF) { + // return ESP_ERR_INVALID_ARG; + _div_num = 8; + } + + + ESP_LOGD("ESP32", "i2s pll_160M_clock_d2 clkm_div_num is: %d", _div_num); + + // I2S_CLK_SEL Set this bit to select I2S module clock source. + // 0: No clock. 1: APLL_CLK. 2: PLL_160M_CLK. 3: No clock. (R/W) + dev->clkm_conf.clk_sel = 2; + dev->clkm_conf.clkm_div_a = 1; // Clock denominator + dev->clkm_conf.clkm_div_b = 0; // Clock numerator + dev->clkm_conf.clkm_div_num = _div_num; + dev->clkm_conf.clk_en = 1; + + // Calc + auto output_freq = (160000000L/_div_num); + + // Calculate clock divider for Original ESP32 + #else + + // Note: clkm_div_num must only be set here AFTER clkm_div_b, clkm_div_a, etc. Or weird things happen! + // On original ESP32, max I2S DMA parallel speed is 20Mhz. + + // 160Mhz is only assured when the CPU clock is 240Mhz on the ESP32... + // [esp32-hal-cpu.c:244] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz + + //static uint32_t pll_d2_clock = (source_freq/2) * 1000 * 1000 >> 1; + + // I2S_CLKM_DIV_NUM 2=40MHz / 3=27MHz / 4=20MHz / 5=16MHz / 8=10MHz / 10=8MHz + //auto _div_num = std::min(255u, 1 + ((pll_d2_clock) / (1 + freq))); + + auto _div_num = 80000000L / freq / i2s_parallel_get_memory_width(ESP32_I2S_DEVICE, 16); // 16 bits in parallel + if(_div_num < 2 || _div_num > 0xFF) { + // return ESP_ERR_INVALID_ARG; + _div_num = 4; + } + + ///auto _div_num = 80000000L/freq; + + ESP_LOGD("ESP32", "i2s pll_d2_clock clkm_div_num is: %ld", _div_num); + + dev->clkm_conf.clka_en=1; // Use the 80mhz system clock (PLL_D2_CLK) when '0' + dev->clkm_conf.clkm_div_a = 1; // Clock denominator + dev->clkm_conf.clkm_div_b = 0; // Clock numerator + dev->clkm_conf.clkm_div_num = _div_num; + + auto output_freq = (80000000L/_div_num); + + #endif + + + output_freq = output_freq + 0; // work around arudino 'unused var' issue if debug isn't enabled. + ESP_LOGI("ESP32/S2", "Output frequency is %ld Mhz??", (output_freq/1000000/i2s_parallel_get_memory_width(ESP32_I2S_DEVICE, 16))); + + + // Setup i2s clock + dev->sample_rate_conf.val = 0; + + // Third stage config, width of data to be written to IO (I think this should always be the actual data width?) + dev->sample_rate_conf.rx_bits_mod = bus_width; + dev->sample_rate_conf.tx_bits_mod = bus_width; + + // Serial clock + // ESP32 and ESP32-S2 TRM clearly say that "Note that I2S_TX_BCK_DIV_NUM[5:0] must not be configured as 1." + dev->sample_rate_conf.rx_bck_div_num = 2; + dev->sample_rate_conf.tx_bck_div_num = 2; + + ////////////////////////////// END CLOCK CONFIGURATION ///////////////////////////////// + + // I2S conf2 reg + dev->conf2.val = 0; + dev->conf2.lcd_en = 1; + dev->conf2.lcd_tx_wrx2_en=0; + dev->conf2.lcd_tx_sdx2_en=0; + + // I2S conf reg + dev->conf.val = 0; + + #if defined (CONFIG_IDF_TARGET_ESP32S2) + dev->conf.tx_dma_equal=1; // esp32-s2 only + dev->conf.pre_req_en=1; // esp32-s2 only - enable I2S to prepare data earlier? wtf? + #endif + + // Now start setting up DMA FIFO + dev->fifo_conf.val = 0; + dev->fifo_conf.rx_data_num = 32; // Thresholds. + dev->fifo_conf.tx_data_num = 32; + dev->fifo_conf.dscr_en = 1; + + #if !defined (CONFIG_IDF_TARGET_ESP32S2) + + // Enable "One datum will be written twice in LCD mode" - for some reason, + // if we don't do this in 8-bit mode, data is updated on half-clocks not clocks + if(_cfg.parallel_width == 8) + dev->conf2.lcd_tx_wrx2_en=1; + + // Not really described for non-pcm modes, although datasheet states it should be set correctly even for LCD mode + // First stage config. Configures how data is loaded into fifo + if(_cfg.parallel_width == 24) { + // Mode 0, single 32-bit channel, linear 32 bit load to fifo + dev->fifo_conf.tx_fifo_mod = 3; + } else { + // Mode 1, single 16-bit channel, load 16 bit sample(*) into fifo and pad to 32 bit with zeros + // *Actually a 32 bit read where two samples are read at once. Length of fifo must thus still be word-aligned + dev->fifo_conf.tx_fifo_mod = 1; + } + + // Dictated by ESP32 datasheet + dev->fifo_conf.rx_fifo_mod_force_en = 1; + dev->fifo_conf.tx_fifo_mod_force_en = 1; + + // Second stage config + dev->conf_chan.val = 0; + + // 16-bit single channel data + dev->conf_chan.tx_chan_mod = 1; + dev->conf_chan.rx_chan_mod = 1; + + #endif + + // Reset FIFO + dev->conf.rx_fifo_reset = 1; + + #if defined (CONFIG_IDF_TARGET_ESP32S2) + while(dev->conf.rx_fifo_reset_st); // esp32-s2 only + #endif + + dev->conf.rx_fifo_reset = 0; + dev->conf.tx_fifo_reset = 1; + + #if defined (CONFIG_IDF_TARGET_ESP32S2) + while(dev->conf.tx_fifo_reset_st); // esp32-s2 only + #endif + dev->conf.tx_fifo_reset = 0; + + + // Reset DMA + dev->lc_conf.in_rst = 1; + dev->lc_conf.in_rst = 0; + dev->lc_conf.out_rst = 1; + dev->lc_conf.out_rst = 0; + + dev->lc_conf.ahbm_rst = 1; + dev->lc_conf.ahbm_rst = 0; + + dev->in_link.val = 0; + dev->out_link.val = 0; + + + // Device reset + dev->conf.rx_reset=1; + dev->conf.tx_reset=1; + dev->conf.rx_reset=0; + dev->conf.tx_reset=0; + + dev->conf1.val = 0; + dev->conf1.tx_stop_en = 0; + dev->timing.val = 0; + + + // If we have double buffering, then allocate an interrupt service routine function + // that can be used for I2S0/I2S1 created interrupts. + + // Setup I2S Interrupt + SET_PERI_REG_BITS(I2S_INT_ENA_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S); + + // Allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete + esp_intr_alloc(irq_source, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL); + + + #if defined (CONFIG_IDF_TARGET_ESP32S2) + ESP_LOGD("ESP32-S2", "init() GPIO and clock configuration set for ESP32-S2"); + #else + ESP_LOGD("ESP32-ORIG", "init() GPIO and clock configuration set for ESP32"); + #endif + + return true; + } + + + void Bus_Parallel16::release(void) + { + if (_dmadesc_a) + { + heap_caps_free(_dmadesc_a); + _dmadesc_a = nullptr; + _dmadesc_count = 0; + } + + if (_dmadesc_b) + { + heap_caps_free(_dmadesc_b); + _dmadesc_b = nullptr; + _dmadesc_count = 0; + } + } + + void Bus_Parallel16::enable_double_dma_desc(void) + { + _double_dma_buffer = true; + } + + // Need this to work for double buffers etc. + bool Bus_Parallel16::allocate_dma_desc_memory(size_t len) + { + if (_dmadesc_a) heap_caps_free(_dmadesc_a); // free all dma descrptios previously + + _dmadesc_count = len; + _dmadesc_last = len-1; + + ESP_LOGI("ESP32/S2", "Allocating memory for %d DMA descriptors.", len); + + _dmadesc_a= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); + + if (_dmadesc_a == nullptr) + { + ESP_LOGE("ESP32/S2", "ERROR: Couldn't malloc _dmadesc_a. Not enough memory."); + return false; + } + + + if (_double_dma_buffer) + { + if (_dmadesc_b) heap_caps_free(_dmadesc_b); // free all dma descrptios previously + + ESP_LOGD("ESP32/S2", "Allocating the second buffer (double buffer enabled)."); + + _dmadesc_b = (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); + + if (_dmadesc_b == nullptr) + { + ESP_LOGE("ESP32/S2", "ERROR: Couldn't malloc _dmadesc_b. Not enough memory."); + _double_dma_buffer = false; + return false; + } + } + + _dmadesc_a_idx = 0; + _dmadesc_b_idx = 0; + + ESP_LOGD("ESP32/S2", "Allocating %d bytes of memory for DMA descriptors.", sizeof(HUB75_DMA_DESCRIPTOR_T) * len); + + // New - Temporary blank descriptor for transitions between DMA buffer + _dmadesc_blank = (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * 1, MALLOC_CAP_DMA); + _dmadesc_blank->size = 1024*2; + _dmadesc_blank->length = 1024*2; + _dmadesc_blank->buf = (uint8_t*) _blank_data; + _dmadesc_blank->eof = 1; + _dmadesc_blank->sosf = 0; + _dmadesc_blank->owner = 1; + _dmadesc_blank->qe.stqe_next = (lldesc_t*) _dmadesc_blank; + _dmadesc_blank->offset = 0; + + return true; + + } + + void Bus_Parallel16::create_dma_desc_link(void *data, size_t size, bool dmadesc_b) + { + static constexpr size_t MAX_DMA_LEN = (4096-4); + + if (size > MAX_DMA_LEN) + { + size = MAX_DMA_LEN; + ESP_LOGW("ESP32/S2", "Creating DMA descriptor which links to payload with size greater than MAX_DMA_LEN!"); + } + + if ( !dmadesc_b ) + { + if ( (_dmadesc_a_idx+1) > _dmadesc_count) { + ESP_LOGE("ESP32/S2", "Attempted to create more DMA descriptors than allocated memory for. Expecting a maximum of %d DMA descriptors", _dmadesc_count); + return; + } + } + + volatile lldesc_t *dmadesc; + volatile lldesc_t *next; + bool eof = false; + + if ( (dmadesc_b == true) ) // for primary buffer + { + dmadesc = &_dmadesc_b[_dmadesc_b_idx]; + + next = (_dmadesc_b_idx < (_dmadesc_last) ) ? &_dmadesc_b[_dmadesc_b_idx+1]:_dmadesc_b; + eof = (_dmadesc_b_idx == (_dmadesc_last)); + } + else + { + dmadesc = &_dmadesc_a[_dmadesc_a_idx]; + + // https://stackoverflow.com/questions/47170740/c-negative-array-index + next = (_dmadesc_a_idx < (_dmadesc_last) ) ? _dmadesc_a + _dmadesc_a_idx+1:_dmadesc_a; + eof = (_dmadesc_a_idx == (_dmadesc_last)); + } + + if ( _dmadesc_a_idx == (_dmadesc_last) ) { + ESP_LOGW("ESP32/S2", "Creating final DMA descriptor and linking back to 0."); + } + + dmadesc->size = size; + dmadesc->length = size; + dmadesc->buf = (uint8_t*) data; + dmadesc->eof = eof; + dmadesc->sosf = 0; + dmadesc->owner = 1; + dmadesc->qe.stqe_next = (lldesc_t*) next; + dmadesc->offset = 0; + + if ( (dmadesc_b == true) ) { // for primary buffer + _dmadesc_b_idx++; + } else { + _dmadesc_a_idx++; + } + + } // end create_dma_desc_link + + void Bus_Parallel16::dma_transfer_start() + { + auto dev = _dev; + + // Configure DMA burst mode + dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN; + + // Set address of DMA descriptor, start with buffer 0 / 'a' + dev->out_link.addr = (uint32_t) _dmadesc_a; + + // Start DMA operation + dev->out_link.stop = 0; + dev->out_link.start = 1; + + dev->conf.tx_start = 1; + + + } // end + + + void Bus_Parallel16::dma_transfer_stop() + { + auto dev = _dev; + + // Stop all ongoing DMA operations + dev->out_link.stop = 1; + dev->out_link.start = 0; + dev->conf.tx_start = 0; + + } // end + + + void Bus_Parallel16::flip_dma_output_buffer(int buffer_id) // pass by reference so we can change in main matrixpanel class + { + + // Setup interrupt handler which is focussed only on the (page 322 of Tech. Ref. Manual) + // "I2S_OUT_EOF_INT: Triggered when rxlink has finished sending a packet" (when dma linked list with eof = 1 is hit) + + if ( buffer_id == 1) { + + _dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; // Start sending out _dmadesc_b (or buffer 1) + + //fix _dmadesc_ loop issue #407 + //need to connect the up comming _dmadesc_ not the old one + _dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; + + } else { + + _dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0]; + _dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0]; + + } + + previousBufferFree = false; + //while (i2s_parallel_is_previous_buffer_free() == false) {} + while (!previousBufferFree); + + + + + } // end flip + + + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32_i2s_parallel_dma.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32_i2s_parallel_dma.hpp new file mode 100644 index 0000000..b0d3642 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32/esp32_i2s_parallel_dma.hpp @@ -0,0 +1,152 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) + + Modified heavily for the ESP32 HUB75 DMA library by: + [mrfaptastic](https://github.com/mrfaptastic) + +------------------------------------------------------------------------------ + + Putin’s Russia and its genocide in Ukraine is a disgrace to humanity. + + https://www.reddit.com/r/ukraine/comments/xfuc6v/more_than_460_graves_have_already_been_found_in/ + + Xi Jinping and his communist China’s silence on the war in Ukraine says everything about + how China condones such genocide, especially if it's against 'the west' (aka. decency). + + Whilst the good people at Espressif probably have nothing to do with this, the unfortunate + reality is libraries like this increase the popularity of Chinese silicon chips, which + indirectly funds (through CCP state taxes) the growth and empowerment of such a despot government. + + Global democracy, decency and security is put at risk with every Chinese silicon chip that is bought. + +/----------------------------------------------------------------------------*/ + +#pragma once + +#include // memcpy +#include +#include + +#include +#include +#include +#include +#include + +#define DMA_MAX (4096-4) + +// The type used for this SoC +#define HUB75_DMA_DESCRIPTOR_T lldesc_t + + +#if defined (CONFIG_IDF_TARGET_ESP32S2) +#define ESP32_I2S_DEVICE I2S_NUM_0 +#else +#define ESP32_I2S_DEVICE I2S_NUM_1 +#endif + +//---------------------------------------------------------------------------- + +void IRAM_ATTR irq_hndlr(void* arg); +i2s_dev_t* getDev(); + +//---------------------------------------------------------------------------- + + class Bus_Parallel16 + { + public: + Bus_Parallel16() + { + + } + + struct config_t + { + // max 20MHz (when in 16 bit / 2 byte mode) + uint32_t bus_freq = 10000000; + int8_t pin_wr = -1; // + int8_t pin_rd = -1; + int8_t pin_rs = -1; // D/C + bool invert_pclk = false; + int8_t parallel_width = 16; // do not change + union + { + int8_t pin_data[16]; + struct + { + int8_t pin_d0; + int8_t pin_d1; + int8_t pin_d2; + int8_t pin_d3; + int8_t pin_d4; + int8_t pin_d5; + int8_t pin_d6; + int8_t pin_d7; + int8_t pin_d8; + int8_t pin_d9; + int8_t pin_d10; + int8_t pin_d11; + int8_t pin_d12; + int8_t pin_d13; + int8_t pin_d14; + int8_t pin_d15; + }; + }; + }; + + const config_t& config(void) const { return _cfg; } + void config(const config_t& config); + + bool init(void) ; + void release(void) ; + + void enable_double_dma_desc(); + bool allocate_dma_desc_memory(size_t len); + + void create_dma_desc_link(void *memory, size_t size, bool dmadesc_b = false); + + void dma_transfer_start(); + void dma_transfer_stop(); + + void flip_dma_output_buffer(int buffer_id); + + private: + + void _init_pins() { }; + + config_t _cfg; + + bool _double_dma_buffer = false; + //bool _dmadesc_a_active = true; + + uint32_t _dmadesc_count = 0; // number of dma decriptors + uint32_t _dmadesc_last = 0; + + uint32_t _dmadesc_a_idx = 0; + uint32_t _dmadesc_b_idx = 0; + + HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr; + HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr; + + HUB75_DMA_DESCRIPTOR_T* _dmadesc_blank = nullptr; + uint16_t _blank_data[1024] = {0}; + + volatile i2s_dev_t* _dev; + + + + }; diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s2/esp32s2-default-pins.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s2/esp32s2-default-pins.hpp new file mode 100644 index 0000000..202b1c5 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s2/esp32s2-default-pins.hpp @@ -0,0 +1,16 @@ +#pragma once + +#define R1_PIN_DEFAULT 45 +#define G1_PIN_DEFAULT 42 +#define B1_PIN_DEFAULT 41 +#define R2_PIN_DEFAULT 40 +#define G2_PIN_DEFAULT 39 +#define B2_PIN_DEFAULT 38 +#define A_PIN_DEFAULT 37 +#define B_PIN_DEFAULT 36 +#define C_PIN_DEFAULT 35 +#define D_PIN_DEFAULT 34 +#define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32 +#define LAT_PIN_DEFAULT 26 +#define OE_PIN_DEFAULT 21 +#define CLK_PIN_DEFAULT 33 diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/ESP32-S3-DevKitC-1-pin-layout.png b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/ESP32-S3-DevKitC-1-pin-layout.png new file mode 100644 index 0000000..c4391b3 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/ESP32-S3-DevKitC-1-pin-layout.png differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/Readme.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/Readme.md new file mode 100644 index 0000000..29cfbd3 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/Readme.md @@ -0,0 +1,24 @@ +https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/external-ram.html + +Restrictions + +External RAM use has the following restrictions: + +When flash cache is disabled (for example, if the flash is being written to), the external RAM also becomes inaccessible; any reads from or writes to it will lead to an illegal cache access exception. This is also the reason why ESP-IDF does not by default allocate any task stacks in external RAM (see below). + + External RAM cannot be used as a place to store DMA transaction descriptors or as a buffer for a DMA transfer to read from or write into. Therefore when External RAM is enabled, any buffer that will be used in combination with DMA must be allocated using heap_caps_malloc(size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL) and can be freed using a standard free() call. + +*Note, although ESP32-S3 has hardware support for DMA to/from external RAM, this is not yet supported in ESP-IDF.* + +External RAM uses the same cache region as the external flash. This means that frequently accessed variables in external RAM can be read and modified almost as quickly as in internal ram. However, when accessing large chunks of data (>32 KB), the cache can be insufficient, and speeds will fall back to the access speed of the external RAM. Moreover, accessing large chunks of data can “push out” cached flash, possibly making the execution of code slower afterwards. + +In general, external RAM will not be used as task stack memory. xTaskCreate() and similar functions will always allocate internal memory for stack and task TCBs. + +Reserved Pins on ESP32-S3: + +![Reserved Pins](ReservedPinsForPSRAM.PNG) + +Devkit Layout: + +![ESP32-S3 DevKit layout](ESP32-S3-DevKitC-1-pin-layout.png) + diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/ReservedPinsForPSRAM.PNG b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/ReservedPinsForPSRAM.PNG new file mode 100644 index 0000000..994fda8 Binary files /dev/null and b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/ReservedPinsForPSRAM.PNG differ diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/esp32s3-default-pins.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/esp32s3-default-pins.hpp new file mode 100644 index 0000000..57d8aae --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/esp32s3-default-pins.hpp @@ -0,0 +1,18 @@ +#pragma once + +// Avoid and QSPI pins + +#define R1_PIN_DEFAULT 4 +#define G1_PIN_DEFAULT 5 +#define B1_PIN_DEFAULT 6 +#define R2_PIN_DEFAULT 7 +#define G2_PIN_DEFAULT 15 +#define B2_PIN_DEFAULT 16 +#define A_PIN_DEFAULT 18 +#define B_PIN_DEFAULT 8 +#define C_PIN_DEFAULT 3 +#define D_PIN_DEFAULT 42 +#define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32 +#define LAT_PIN_DEFAULT 40 +#define OE_PIN_DEFAULT 2 +#define CLK_PIN_DEFAULT 41 diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/gdma_lcd_parallel16.cpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/gdma_lcd_parallel16.cpp new file mode 100644 index 0000000..d064210 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/gdma_lcd_parallel16.cpp @@ -0,0 +1,449 @@ +/********************************************************************************************* + Simple example of using the ESP32-S3's LCD peripheral for general-purpose + (non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer), + cycles through a pattern among them at about 1 Hz. + This code is ONLY for the ESP32-S3, NOT the S2, C3 or original ESP32. + None of this is authoritative canon, just a lot of trial error w/datasheet + and register poking. Probably more robust ways of doing this still TBD. + + + FULL CREDIT goes to AdaFruit and https://github.com/PaintYourDragon + + https://blog.adafruit.com/2022/06/21/esp32uesday-more-s3-lcd-peripheral-hacking-with-code/ + + https://github.com/adafruit/Adafruit_Protomatter/blob/master/src/arch/esp32-s3.h + + PLEASE SUPPORT THEM! + + ********************************************************************************************/ +#if __has_include () +// Stop compile errors: /src/platforms/esp32s3/gdma_lcd_parallel16.hpp:64:10: fatal error: hal/lcd_ll.h: No such file or directory + + #ifdef ARDUINO_ARCH_ESP32 + #include + #endif + + #include "gdma_lcd_parallel16.hpp" + #include "esp_attr.h" + +/* + dma_descriptor_t desc; // DMA descriptor for testing + + uint8_t data[8][312]; // Transmit buffer (2496 bytes total) + uint16_t* dmabuff2; +*/ + + DRAM_ATTR volatile bool previousBufferFree = true; + + // End-of-DMA-transfer callback + IRAM_ATTR bool gdma_on_trans_eof_callback(gdma_channel_handle_t dma_chan, + gdma_event_data_t *event_data, void *user_data) { + + // This DMA callback seems to trigger a moment before the last data has + // issued (buffering between DMA & LCD peripheral?), so pause a moment + // before stopping LCD data out. The ideal delay may depend on the LCD + // clock rate...this one was determined empirically by monitoring on a + // logic analyzer. YMMV. + esp_rom_delay_us(100); + // The LCD peripheral stops transmitting at the end of the DMA xfer, but + // clear the lcd_start flag anyway -- we poll it in loop() to decide when + // the transfer has finished, and the same flag is set later to trigger + // the next transfer. + + //LCD_CAM.lcd_user.lcd_start = 0; + + previousBufferFree = true; + + return true; + } + + lcd_cam_dev_t* getDev() + { + return &LCD_CAM; + } + + // ------------------------------------------------------------------------------ + + void Bus_Parallel16::config(const config_t& cfg) + { + _cfg = cfg; + //auto port = cfg.port; + _dev = getDev(); + } + + + //https://github.com/adafruit/Adafruit_Protomatter/blob/master/src/arch/esp32-s3.h + bool Bus_Parallel16::init(void) + { + ///dmabuff2 = (uint16_t*)heap_caps_malloc(sizeof(uint16_t) * 64*32, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + + // LCD_CAM peripheral isn't enabled by default -- MUST begin with this: + periph_module_enable(PERIPH_LCD_CAM_MODULE); + periph_module_reset(PERIPH_LCD_CAM_MODULE); + + // Reset LCD bus + LCD_CAM.lcd_user.lcd_reset = 1; + esp_rom_delay_us(1000); + +// uint32_t lcd_clkm_div_num = ((160000000 + 1) / _cfg.bus_freq); +// ESP_LOGI("", "Clock divider is %d", lcd_clkm_div_num); + + // Configure LCD clock. Since this program generates human-perceptible + // output and not data for LED matrices or NeoPixels, use almost the + // slowest LCD clock rate possible. The S3-mini module used on Feather + // ESP32-S3 has a 40 MHz crystal. A 2-stage clock division of 1:16000 + // is applied (250*64), yielding 2,500 Hz. Still much too fast for + // human eyes, so later we set up the data to repeat each output byte + // many times over. + //LCD_CAM.lcd_clock.clk_en = 0; // Enable peripheral clock + + // LCD_CAM_LCD_CLK_SEL Select LCD module source clock. 0: clock source is disabled. 1: XTAL_CLK. 2: PLL_D2_CLK. 3: PLL_F160M_CLK. (R/W) + LCD_CAM.lcd_clock.lcd_clk_sel = 3; // Use 160Mhz Clock Source + + LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in 1st half cycle + LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle + + LCD_CAM.lcd_clock.lcd_clkcnt_n = 1; // Should never be zero + + //LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 0; // PCLK = CLK / (CLKCNT_N+1) + LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK / 1 (... so 160Mhz still) + + + // https://esp32.com/viewtopic.php?f=5&t=24459&start=80#p94487 + /* Re: ESP32-S3 LCD and I2S FULL documentation + * by ESP_Sprite » Fri Mar 25, 2022 2:06 am + * + * Are you sure you are staying within the limits of the psram throughput? If GDMA can't fetch data fast + * enough it leads to corruption. Also keep in mind that worst case scenario, the gdma can only use half of + * the bandwidth of the psram peripheral (as it's round-robin shared with the CPUs). + */ + + // Fastest speed I can get with Octoal PSRAM to work before nothing shows. Based on manual testing. + // If using an ESP32-S3 with slower (half the bandwidth) Q-SPI (Quad), then the divisor will need to be '20' (8Mhz) which wil be flickery! + if (_cfg.psram_clk_override) + { + ESP_LOGI("S3", "DMA buffer is on PSRAM. Limiting clockspeed...."); + //LCD_CAM.lcd_clock.lcd_clkm_div_num = 10; //16mhz is the fasted the Octal PSRAM can support it seems from faptastic's testing using an N8R8 variant (Octal SPI PSRAM). + + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/441#issuecomment-1513631890 + LCD_CAM.lcd_clock.lcd_clkm_div_num = 12; // 13Mhz is the fastest when the DMA memory is needed to service other peripherals as well. + } + else + { + + auto freq = (_cfg.bus_freq); + + auto _div_num = 8; // 20Mhz + if (freq < 20000000L) { + _div_num = 12; // 13Mhz + } + else if (freq > 20000000L) { + _div_num = 6; // 26Mhz --- likely to have noise without a good connection + } + + //LCD_CAM.lcd_clock.lcd_clkm_div_num = lcd_clkm_div_num; + LCD_CAM.lcd_clock.lcd_clkm_div_num = _div_num; //3; + + } + + ESP_LOGI("S3", "Clock divider is %d", LCD_CAM.lcd_clock.lcd_clkm_div_num); + ESP_LOGD("S3", "Resulting output clock frequency: %ld Mhz", (160000000L/LCD_CAM.lcd_clock.lcd_clkm_div_num)); + + + LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 0/1 fractional divide + LCD_CAM.lcd_clock.lcd_clkm_div_b = 0; + + // See section 26.3.3.1 of the ESP32­S3 Technical Reference Manual + // for information on other clock sources and dividers. + + // Configure LCD frame format. This is where we fiddle the peripheral + // to provide generic 8-bit output rather than actually driving an LCD. + // There's also a 16-bit mode but that's not shown here. + LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB) + LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter + LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame + + LCD_CAM.lcd_misc.lcd_bk_en = 1; // https://esp32.com/viewtopic.php?t=24459&start=60#p91835 + + LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays + LCD_CAM.lcd_user.lcd_always_out_en = 1; // Enable 'always out' mode + LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes + LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order + LCD_CAM.lcd_user.lcd_2byte_en = 1; // 8-bit data mode + LCD_CAM.lcd_user.lcd_dummy = 1; // Dummy phase(s) @ LCD start + LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 1+1 dummy phase + LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start + // "Dummy phases" are initial LCD peripheral clock cycles before data + // begins transmitting when requested. After much testing, determined + // that at least one dummy phase MUST be enabled for DMA to trigger + // reliably. A problem with dummy phase(s) is if we're also using the + // LCD_PCLK_IDX signal (not used in this code, but Adafruit_Protomatter + // does)...the clock signal will start a couple of pulses before data, + // which may or may not be problematic in some situations. You can + // disable the dummy phase but need to keep the LCD TX FIFO primed + // in that case, which gets complex. + // always_out_en is set above to allow aribtrary-length transfers, + // else lcd_dout_cyclelen is used...but is limited to 8K. Long (>4K) + // transfers need DMA linked lists, not used here but mentioned later. + + // Route 8 LCD data signals to GPIO pins + int8_t* pins = _cfg.pin_data; + + for(int i = 0; i < 16; i++) + { + if (pins[i] >= 0) { // -1 value will CRASH the ESP32! + esp_rom_gpio_connect_out_signal(pins[i], LCD_DATA_OUT0_IDX + i, false, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pins[i]], PIN_FUNC_GPIO); + gpio_set_drive_capability((gpio_num_t)pins[i], (gpio_drive_cap_t)3); + } + } + + /* + const struct { + int8_t pin; + uint8_t signal; + } mux[] = { + { 43, LCD_DATA_OUT0_IDX }, // These are 8 consecutive pins down one + { 42, LCD_DATA_OUT1_IDX }, // side of the ESP32-S3 Feather. The ESP32 + { 2, LCD_DATA_OUT2_IDX }, // has super flexible pin MUX capabilities, + { 9, LCD_DATA_OUT3_IDX }, // so any signal can go to any pin! + { 10, LCD_DATA_OUT4_IDX }, + { 11, LCD_DATA_OUT5_IDX }, + { 12, LCD_DATA_OUT6_IDX }, + { 13, LCD_DATA_OUT7_IDX }, + }; + for (int i = 0; i < 8; i++) { + esp_rom_gpio_connect_out_signal(mux[i].pin, LCD_DATA_OUT0_IDX + i, false, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[mux[i].pin], PIN_FUNC_GPIO); + gpio_set_drive_capability((gpio_num_t)mux[i].pin, (gpio_drive_cap_t)3); + } +*/ + // Clock + esp_rom_gpio_connect_out_signal(_cfg.pin_wr, LCD_PCLK_IDX, _cfg.invert_pclk, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[_cfg.pin_wr], PIN_FUNC_GPIO); + gpio_set_drive_capability((gpio_num_t)_cfg.pin_wr, (gpio_drive_cap_t)3); + + // This program has a known fixed-size data buffer (2496 bytes) that fits + // in a single DMA descriptor (max 4095 bytes). Large transfers would + // require a linked list of descriptors, but here it's just one... + +/* + desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc.dw0.suc_eof = 0; // Last descriptor + desc.next = &desc; // No linked list +*/ + + // Remaining descriptor elements are initialized before each DMA transfer. + + // Allocate DMA channel and connect it to the LCD peripheral + static gdma_channel_alloc_config_t dma_chan_config = { + .sibling_chan = NULL, + .direction = GDMA_CHANNEL_DIRECTION_TX, + .flags = { + .reserve_sibling = 0 + } + }; + gdma_new_channel(&dma_chan_config, &dma_chan); + gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); + static gdma_strategy_config_t strategy_config = { + .owner_check = false, + .auto_update_desc = false + }; + gdma_apply_strategy(dma_chan, &strategy_config); + + gdma_transfer_ability_t ability = { + .sram_trans_align = 32, + .psram_trans_align = 64, + }; + gdma_set_transfer_ability(dma_chan, &ability); + + // Enable DMA transfer callback + static gdma_tx_event_callbacks_t tx_cbs = { + // .on_trans_eof is literally the only gdma tx event type available + .on_trans_eof = gdma_on_trans_eof_callback + }; + gdma_register_tx_event_callbacks(dma_chan, &tx_cbs, NULL); + + + // This uses a busy loop to wait for each DMA transfer to complete... + // but the whole point of DMA is that one's code can do other work in + // the interim. The CPU is totally free while the transfer runs! + while (LCD_CAM.lcd_user.lcd_start); // Wait for DMA completion callback + + // After much experimentation, each of these steps is required to get + // a clean start on the next LCD transfer: + gdma_reset(dma_chan); // Reset DMA to known state + esp_rom_delay_us(1000); + + LCD_CAM.lcd_user.lcd_dout = 1; // Enable data out + LCD_CAM.lcd_user.lcd_update = 1; // Update registers + LCD_CAM.lcd_misc.lcd_afifo_reset = 1; // Reset LCD TX FIFO + + + return true; // no return val = illegal instruction + } + + + void Bus_Parallel16::release(void) + { + if (_i80_bus) + { + esp_lcd_del_i80_bus(_i80_bus); + } + if (_dmadesc_a) + { + heap_caps_free(_dmadesc_a); + _dmadesc_a = nullptr; + _dmadesc_count = 0; + } + + } + + void Bus_Parallel16::enable_double_dma_desc(void) + { + ESP_LOGI("S3", "Enabled support for secondary DMA buffer."); + _double_dma_buffer = true; + } + + // Need this to work for double buffers etc. + bool Bus_Parallel16::allocate_dma_desc_memory(size_t len) + { + if (_dmadesc_a) heap_caps_free(_dmadesc_a); // free all dma descrptios previously + _dmadesc_count = len; + + ESP_LOGD("S3", "Allocating %d bytes memory for DMA descriptors.", sizeof(HUB75_DMA_DESCRIPTOR_T) * len); + + _dmadesc_a= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); + + if (_dmadesc_a == nullptr) + { + ESP_LOGE("S3", "ERROR: Couldn't malloc _dmadesc_a. Not enough memory."); + return false; + } + + if (_double_dma_buffer) + { + _dmadesc_b= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); + + if (_dmadesc_b == nullptr) + { + ESP_LOGE("S3", "ERROR: Couldn't malloc _dmadesc_b. Not enough memory."); + _double_dma_buffer = false; + } + } + + /// override static + _dmadesc_a_idx = 0; + _dmadesc_b_idx = 0; + + return true; + + } + + void Bus_Parallel16::create_dma_desc_link(void *data, size_t size, bool dmadesc_b) + { + static constexpr size_t MAX_DMA_LEN = (4096-4); + + if (size > MAX_DMA_LEN) { + size = MAX_DMA_LEN; + ESP_LOGW("S3", "Creating DMA descriptor which links to payload with size greater than MAX_DMA_LEN!"); + } + + if ( dmadesc_b == true) + { + + _dmadesc_b[_dmadesc_b_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + //_dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = 0; + _dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = (_dmadesc_b_idx == (_dmadesc_count-1)); + _dmadesc_b[_dmadesc_b_idx].dw0.size = _dmadesc_b[_dmadesc_b_idx].dw0.length = size; //sizeof(data); + _dmadesc_b[_dmadesc_b_idx].buffer = data; //data; + + if (_dmadesc_b_idx == _dmadesc_count-1) { + _dmadesc_b[_dmadesc_b_idx].next = (dma_descriptor_t *) &_dmadesc_b[0]; + } + else { + _dmadesc_b[_dmadesc_b_idx].next = (dma_descriptor_t *) &_dmadesc_b[_dmadesc_b_idx+1]; + } + + _dmadesc_b_idx++; + + + } + else + { + + if ( _dmadesc_a_idx >= _dmadesc_count) + { + ESP_LOGE("S3", "Attempted to create more DMA descriptors than allocated. Expecting max %" PRIu32 " descriptors.", _dmadesc_count); + return; + } + + _dmadesc_a[_dmadesc_a_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + //_dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = 0; + _dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = (_dmadesc_a_idx == (_dmadesc_count-1)); + _dmadesc_a[_dmadesc_a_idx].dw0.size = _dmadesc_a[_dmadesc_a_idx].dw0.length = size; //sizeof(data); + _dmadesc_a[_dmadesc_a_idx].buffer = data; //data; + + if (_dmadesc_a_idx == _dmadesc_count-1) { + _dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[0]; + } + else { + _dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[_dmadesc_a_idx+1]; + } + + _dmadesc_a_idx++; + + + } + + } // end create_dma_desc_link + + void Bus_Parallel16::dma_transfer_start() + { + gdma_start(dma_chan, (intptr_t)&_dmadesc_a[0]); // Start DMA w/updated descriptor(s) + esp_rom_delay_us(100); // Must 'bake' a moment before... + LCD_CAM.lcd_user.lcd_start = 1; // Trigger LCD DMA transfer + + } // end + + void Bus_Parallel16::dma_transfer_stop() + { + + LCD_CAM.lcd_user.lcd_reset = 1; // Trigger LCD DMA transfer + LCD_CAM.lcd_user.lcd_update = 1; // Trigger LCD DMA transfer + + gdma_stop(dma_chan); + + } // end + + + void Bus_Parallel16::flip_dma_output_buffer(int back_buffer_id) + { + + // if ( _double_dma_buffer == false) return; + + if ( back_buffer_id == 1) // change across to everything 'b'' + { + _dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; + _dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; + } + else + { + _dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0]; + _dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0]; + } + + //current_back_buffer_id ^= 1; + + previousBufferFree = false; + + //while (i2s_parallel_is_previous_buffer_free() == false) {} + while (!previousBufferFree); + + + + + } // end flip + + +#endif diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/gdma_lcd_parallel16.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/gdma_lcd_parallel16.hpp new file mode 100644 index 0000000..958b65c --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/esp32s3/gdma_lcd_parallel16.hpp @@ -0,0 +1,176 @@ +/* + Simple example of using the ESP32-S3's LCD peripheral for general-purpose + (non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer), + cycles through a pattern among them at about 1 Hz. + This code is ONLY for the ESP32-S3, NOT the S2, C3 or original ESP32. + None of this is authoritative canon, just a lot of trial error w/datasheet + and register poking. Probably more robust ways of doing this still TBD. + + + FULL CREDIT goes to AdaFruit + + https://blog.adafruit.com/2022/06/21/esp32uesday-more-s3-lcd-peripheral-hacking-with-code/ + + PLEASE SUPPORT THEM! + + + Putin’s Russia and its genocide in Ukraine is a disgrace to humanity. + + https://www.reddit.com/r/ukraine/comments/xfuc6v/more_than_460_graves_have_already_been_found_in/ + + +/---------------------------------------------------------------------------- + +*/ + +#pragma once + +#if __has_include () + +#include +#include + +//#include +#include + +#include +#include + +#include +#include + + +#include +#include + + + +#include +#include + +#include + +#if (ESP_IDF_VERSION_MAJOR == 5) +#include +#else +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +#if __has_include () + #include +#else + #include +#endif + +#if __has_include() + #include +#endif + +#define DMA_MAX (4096-4) + +// The type used for this SoC +#define HUB75_DMA_DESCRIPTOR_T dma_descriptor_t + + +//---------------------------------------------------------------------------- + + class Bus_Parallel16 + { + public: + Bus_Parallel16() + { + + } + + struct config_t + { + // LCD_CAM peripheral number. No need to change (only 0 for ESP32-S3.) + //int port = 0; + + // max 40MHz (when in 16 bit / 2 byte mode) + uint32_t bus_freq = 10000000; + int8_t pin_wr = -1; + int8_t pin_rd = -1; + int8_t pin_rs = -1; // D/C + bool invert_pclk = false; + bool psram_clk_override = false; + union + { + int8_t pin_data[16]; + struct + { + int8_t pin_d0; + int8_t pin_d1; + int8_t pin_d2; + int8_t pin_d3; + int8_t pin_d4; + int8_t pin_d5; + int8_t pin_d6; + int8_t pin_d7; + int8_t pin_d8; + int8_t pin_d9; + int8_t pin_d10; + int8_t pin_d11; + int8_t pin_d12; + int8_t pin_d13; + int8_t pin_d14; + int8_t pin_d15; + }; + }; + }; + + const config_t& config(void) const { return _cfg; } + void config(const config_t& config); + + bool init(void) ; + + void release(void) ; + + void enable_double_dma_desc(); + bool allocate_dma_desc_memory(size_t len); + + void create_dma_desc_link(void *memory, size_t size, bool dmadesc_b = false); + + void dma_transfer_start(); + void dma_transfer_stop(); + + void flip_dma_output_buffer(int back_buffer_id); + + private: + + config_t _cfg; + + volatile lcd_cam_dev_t* _dev; + gdma_channel_handle_t dma_chan; + + uint32_t _dmadesc_count = 0; // number of dma decriptors + + uint32_t _dmadesc_a_idx = 0; + uint32_t _dmadesc_b_idx = 0; + + HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr; + HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr; + + bool _double_dma_buffer = false; + //bool _dmadesc_a_active = true; + + esp_lcd_i80_bus_handle_t _i80_bus; + + + }; + + +#endif \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/platform_detect.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/platform_detect.hpp new file mode 100644 index 0000000..9b44356 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/src/platforms/platform_detect.hpp @@ -0,0 +1,73 @@ +/*----------------------------------------------------------------------------/ +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) + +Modified heavily for the ESP32 HUB75 DMA library by: + [mrfaptastic](https://github.com/mrfaptastic) +/----------------------------------------------------------------------------*/ +#pragma once + +#if defined (ESP_PLATFORM) + + #include + + #if defined (CONFIG_IDF_TARGET_ESP32C3) + + #error "ERROR: ESP32C3 not supported." + + #elif defined (CONFIG_IDF_TARGET_ESP32S2) + + #pragma message "Compiling for ESP32-S2" + #include "esp32/esp32_i2s_parallel_dma.hpp" + #include "esp32s2/esp32s2-default-pins.hpp" + + + #elif defined (CONFIG_IDF_TARGET_ESP32S3) + + #pragma message "Compiling for ESP32-S3" + #include "esp32s3/gdma_lcd_parallel16.hpp" + #include "esp32s3/esp32s3-default-pins.hpp" + + #if defined(SPIRAM_FRAMEBUFFER) && defined (CONFIG_IDF_TARGET_ESP32S3) + #pragma message "Enabling use of PSRAM/SPIRAM based DMA Buffer" + #define SPIRAM_DMA_BUFFER 1 + + // Disable fast functions because I don't understand the interaction with DMA PSRAM and the CPU->DMA->SPIRAM Cache implications.. + #define NO_FAST_FUNCTIONS 1 + #endif + + #elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32H2) + + #error "ESP32 RISC-V devices do not have an LCD interface and are therefore not supported by this library." + + #elif defined (CONFIG_IDF_TARGET_ESP32) || defined(ESP32) + + // Assume an ESP32 (the original 2015 version) + // Same include as ESP32S3 + //#pragma message "Compiling for original ESP32 (released 2016)" + + #define ESP32_THE_ORIG 1 + //#include "esp32/esp32_i2s_parallel_dma.hpp" + //#include "esp32/esp32_i2s_parallel_dma.h" + #include "esp32/esp32_i2s_parallel_dma.hpp" + #include "esp32/esp32-default-pins.hpp" + + #else + #error "Unknown platform." + + #endif + + +#endif + diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/README.md b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/README.md new file mode 100644 index 0000000..fe6137f --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/README.md @@ -0,0 +1,5 @@ +Sample app to simulate the VirtualMatrixPanel class for testing / optimisation, without having to test with physical panels. + +``` +g++ -o myapp.exe virtual.cpp +``` \ No newline at end of file diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/baseline.hpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/baseline.hpp new file mode 100644 index 0000000..43d13e7 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/baseline.hpp @@ -0,0 +1,189 @@ + +/** + * Calculate virtual->real co-ordinate mapping to underlying single chain of panels connected to ESP32. + * Updates the private class member variable 'coords', so no need to use the return value. + * Not thread safe, but not a concern for ESP32 sketch anyway... I think. + */ +// DO NOT CHANGE +inline VirtualCoords VirtualMatrixPanelTest::getCoords_WorkingBaslineMarch2023(int16_t virt_x, int16_t virt_y) +{ + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + + if (virt_x < 0 || virt_x >= virtualResX || virt_y < 0 || virt_y >= virtualResY) + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + return coords; + } + + // Do we want to rotate? + if (_rotate) + { + int16_t temp_x = virt_x; + virt_x = virt_y; + virt_y = virtualResY - 1 - temp_x; + } + + int row = (virt_y / panelResY); // 0 indexed + switch(panel_chain_type) + { + + case (CHAIN_TOP_RIGHT_DOWN): + { + if ( (row % 2) == 1 ) + { // upside down panel + + //Serial.printf("Condition 1, row %d ", row); + + // refersed for the row + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + case (CHAIN_TOP_LEFT_DOWN): // OK -> modulus opposite of CHAIN_TOP_RIGHT_DOWN + { + if ( (row % 2) == 0 ) + { // refersed panel + + //Serial.printf("Condition 1, row %d ", row); + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + + + case (CHAIN_BOTTOM_LEFT_UP): // + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 1 ) + { + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP): // OK -> modulus opposite of CHAIN_BOTTOM_LEFT_UP + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 0 ) + { // right side up + + // Serial.printf("Condition 1, row %d ", row); + // refersed for the row + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + + default: + return coords; + break; + + } // end switch + + + + /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the + * the underlying hardware library is designed for (because + * there's only 2 x RGB pins... and convert this to 1/4 or something + */ + if (panel_scan_rate == FOUR_SCAN_32PX_HIGH) + { + /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at + on the panel or chain of panels, per the chaining configuration) to a 1/8 panels + double 'stretched' and 'squished' coordinates which is what needs to be sent from the + DMA buffer. + + Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup + as if the panel is 2 * W and 0.5 * H ! + */ + + if ((virt_y & 8) == 0) + { + coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + // http://cpp.sh/4ak5u + // Real number of DMA y rows is half reality + // coords.y = (y / 16)*8 + (y & 0b00000111); + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + + } + else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH) + { + if ((virt_y & 8) == 0) + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + if (virt_y < 32) + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + else + { + coords.y = ((virt_y - 32) >> 4) * 8 + (virt_y & 0b00000111); + coords.x += 256; + } + } + + return coords; +} diff --git a/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/virtual.cpp b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/virtual.cpp new file mode 100644 index 0000000..168acb1 --- /dev/null +++ b/pixelart-controller/lib/ESP32-HUB75-MatrixPanel-DMA/testing/virtual.cpp @@ -0,0 +1,459 @@ +#include +#include +#include + + +struct VirtualCoords +{ + int16_t x; + int16_t y; + int16_t virt_row; // chain of panels row + int16_t virt_col; // chain of panels col + + VirtualCoords() : x(0), y(0) + { + } +}; + +enum PANEL_SCAN_RATE +{ + NORMAL_TWO_SCAN, NORMAL_ONE_SIXTEEN, // treated as the same + FOUR_SCAN_32PX_HIGH, + FOUR_SCAN_16PX_HIGH +}; + +// Chaining approach... From the perspective of the DISPLAY / LED side of the chain of panels. +enum PANEL_CHAIN_TYPE +{ + CHAIN_TOP_LEFT_DOWN, + CHAIN_TOP_RIGHT_DOWN, + CHAIN_BOTTOM_LEFT_UP, + CHAIN_BOTTOM_RIGHT_UP, + CHAIN_TOP_RIGHT_DOWN_ZZ, /// ZigZag chaining. Might need a big ass cable to do this, all panels right way up. + CHAIN_BOTTOM_RIGHT_UP_ZZ +}; + + +class VirtualMatrixPanelTest +{ + +public: + VirtualMatrixPanelTest(int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, PANEL_CHAIN_TYPE _panel_chain_type = CHAIN_TOP_RIGHT_DOWN) + { + + panelResX = _panelResX; + panelResY = _panelResY; + + vmodule_rows = _vmodule_rows; + vmodule_cols = _vmodule_cols; + + virtualResX = vmodule_cols * _panelResX; + virtualResY = vmodule_rows * _panelResY; + + dmaResX = panelResX * vmodule_rows * vmodule_cols; + + panel_chain_type = _panel_chain_type; + + /* Virtual Display width() and height() will return a real-world value. For example: + * Virtual Display width: 128 + * Virtual Display height: 64 + * + * So, not values that at 0 to X-1 + */ + + coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + + switch (panel_chain_type) { + case CHAIN_TOP_LEFT_DOWN: + chain_type_str = "CHAIN_TOP_LEFT_DOWN"; + break; + + case CHAIN_TOP_RIGHT_DOWN: + chain_type_str = "CHAIN_TOP_RIGHT_DOWN"; + break; + + case CHAIN_TOP_RIGHT_DOWN_ZZ: + chain_type_str = "CHAIN_TOP_RIGHT_DOWN_ZZ"; + break; + + case CHAIN_BOTTOM_RIGHT_UP: + chain_type_str = "CHAIN_BOTTOM_RIGHT_UP"; + break; + + case CHAIN_BOTTOM_LEFT_UP: + chain_type_str = "CHAIN_BOTTOM_LEFT_UP"; + break; + + default: + chain_type_str = "WTF!"; + break; + } + std::cout << "\n\n***************************************************************************\n"; + std::cout << "Chain type: " << chain_type_str << " "; + std::printf("Testing chain of panels of %d rows, %d columns, %d px by %d px resolution. \n\n", vmodule_rows, vmodule_cols, panelResX, panelResX, panelResY); + + + } + + // equivalent methods of the matrix library so it can be just swapped out. + void drawPixel(int16_t x, int16_t y, int16_t expected_x, int16_t expected_y); + + std::string chain_type_str = "UNKNOWN"; + + // Internal co-ord conversion function + VirtualCoords getCoords_Dev(int16_t x, int16_t y); + + VirtualCoords getCoords_WorkingBaslineMarch2023(int16_t x, int16_t y); + + VirtualCoords coords; + + +private: + + int16_t virtualResX; + int16_t virtualResY; + + int16_t vmodule_rows; + int16_t vmodule_cols; + + int16_t panelResX; + int16_t panelResY; + + int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it) + + PANEL_CHAIN_TYPE panel_chain_type; + PANEL_SCAN_RATE panel_scan_rate = NORMAL_TWO_SCAN; + + bool _rotate = false; + +}; // end Class header + +#include "baseline.hpp" + +/** + * Development version for testing. + */ +inline VirtualCoords VirtualMatrixPanelTest::getCoords_Dev(int16_t virt_x, int16_t virt_y) +{ + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + + if (virt_x < 0 || virt_x >= virtualResX || virt_y < 0 || virt_y >= virtualResY) + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + return coords; + } + + // Do we want to rotate? + if (_rotate) + { + int16_t temp_x = virt_x; + virt_x = virt_y; + virt_y = virtualResY - 1 - temp_x; + } + + int row = (virt_y / panelResY); // 0 indexed + switch(panel_chain_type) + { + + case (CHAIN_TOP_RIGHT_DOWN): + { + if ( (row % 2) == 1 ) + { // upside down panel + + //Serial.printf("Condition 1, row %d ", row); + + // refersed for the row + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + case (CHAIN_TOP_LEFT_DOWN): // OK -> modulus opposite of CHAIN_TOP_RIGHT_DOWN + { + if ( (row % 2) == 0 ) + { // refersed panel + + //Serial.printf("Condition 1, row %d ", row); + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + + + case (CHAIN_BOTTOM_LEFT_UP): // + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 1 ) + { + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP): // OK -> modulus opposite of CHAIN_BOTTOM_LEFT_UP + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 0 ) + { // right side up + + // Serial.printf("Condition 1, row %d ", row); + // refersed for the row + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + case CHAIN_TOP_RIGHT_DOWN_ZZ: + { + // Right side up. Starting from top left all the way down. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + case CHAIN_BOTTOM_RIGHT_UP_ZZ: + { + // Right side up. Starting from top left all the way down. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + //Serial.printf("Condition 2, row %d ", row); + coords.x = (row*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + + + + default: + return coords; + break; + + } // end switch + + + + /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the + * the underlying hardware library is designed for (because + * there's only 2 x RGB pins... and convert this to 1/4 or something + */ + if (panel_scan_rate == FOUR_SCAN_32PX_HIGH) + { + /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at + on the panel or chain of panels, per the chaining configuration) to a 1/8 panels + double 'stretched' and 'squished' coordinates which is what needs to be sent from the + DMA buffer. + + Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup + as if the panel is 2 * W and 0.5 * H ! + */ + + if ((virt_y & 8) == 0) + { + coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + // http://cpp.sh/4ak5u + // Real number of DMA y rows is half reality + // coords.y = (y / 16)*8 + (y & 0b00000111); + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + + } + else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH) + { + if ((virt_y & 8) == 0) + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + if (virt_y < 32) + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + else + { + coords.y = ((virt_y - 32) >> 4) * 8 + (virt_y & 0b00000111); + coords.x += 256; + } + } + + return coords; +} + +bool check(VirtualCoords expected, VirtualCoords result, int x = -1, int y = -1) +{ + + if ( result.x != expected.x || result.y != expected.y ) + { + std::printf("Requested (%d, %d) -> expecting physical (%d, %d) got (%d, %d).", x, y, expected.x, expected.y, result.x, result.y); + std::cout << "\t *** FAIL ***\n "; + std::cout << "\n"; + + return false; + } + else + { + return true; + } +} + + +main(int argc, char* argv[]) +{ + std::cout << "Starting Testing...\n"; + + std::list chain_t_test_list { CHAIN_TOP_LEFT_DOWN, CHAIN_TOP_RIGHT_DOWN, CHAIN_BOTTOM_LEFT_UP, CHAIN_BOTTOM_RIGHT_UP }; + + + // Draw pixel at virtual position 70x, 70y = + // x, y x, y + + // x == horizontal + // y = vert :-) + + // 192 x 192 pixel virtual display + int rows = 3; + int cols = 3; + int panel_width_x = 64; + int panel_height_y = 64; + + std::string panel_scan_type = "NORMAL_TWO_SCAN"; + + for (auto chain_t : chain_t_test_list) { + + + VirtualMatrixPanelTest test = VirtualMatrixPanelTest(rows,cols,panel_width_x,panel_height_y, chain_t); + int pass_counter = 0; + int fail_counter = 0; + for (int16_t x = 0; x < panel_width_x*cols; x++) + { + for (int16_t y = 0; y < panel_height_y*rows; y++) + { + VirtualCoords expected = test.getCoords_WorkingBaslineMarch2023(x,y); + VirtualCoords result = test.getCoords_Dev(x,y); + + bool chk_result = check(expected, result, x, y); + + if ( chk_result ) + { + fail_counter++; + } + else + { + pass_counter++; + } + } + } + + if ( fail_counter > 0) { + std::printf("ERROR: %d tests failed.\n", fail_counter); + } else{ + std::printf("SUCCESS: %d coord tests passed.\n", pass_counter); + } + + } // end chain type test list + + + std::cout << "Performing NON-SERPENTINE (ZIG ZAG) TEST"; + + rows = 3; + cols = 1; + panel_width_x = 64; + panel_height_y = 64; + + VirtualMatrixPanelTest test = VirtualMatrixPanelTest(rows,cols,panel_width_x,panel_height_y, CHAIN_TOP_RIGHT_DOWN_ZZ); + + // CHAIN_TOP_RIGHT_DOWN_ZZ test 1 + // (x,y) + VirtualCoords result = test.getCoords_Dev(0,0); + VirtualCoords expected; expected.x = 64*2; expected.y = 0; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + // CHAIN_TOP_RIGHT_DOWN_ZZ test 2 + result = test.getCoords_Dev(10,64*3-1); + expected.x = 10; expected.y = 63; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + + // CHAIN_TOP_RIGHT_DOWN_ZZ test 3 + result = test.getCoords_Dev(16,64*2-1); + expected.x = 80; expected.y = 63; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + + + // CHAIN_BOTTOM_RIGHT_UP_ZZ test 4 + result = test.getCoords_Dev(0,0); + expected.x = 0; expected.y = 0; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + // CHAIN_BOTTOM_RIGHT_UP_ZZ test 4 + result = test.getCoords_Dev(63,64); + expected.x = 64*2-1; expected.y = 0; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + + + std::cout << "\n\n"; + + return 0; +} \ No newline at end of file diff --git a/pixelart-controller/lib/ESPAsyncDNSServer/README.md b/pixelart-controller/lib/ESPAsyncDNSServer/README.md deleted file mode 100644 index 2467477..0000000 --- a/pixelart-controller/lib/ESPAsyncDNSServer/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# AsyncDNSServer: an asynchronous DNS server for the ESP - -Built on AsyncUDP, inspired by the standard DNSServer implementation. - -Usage is almost a drop-in replacement for DNSServer. The only usage difference is not calling handleNextRequest() in the loop (no need for it). diff --git a/pixelart-controller/lib/ESPAsyncDNSServer/examples/AsyncDNSServer/AsyncDNSServer.ino b/pixelart-controller/lib/ESPAsyncDNSServer/examples/AsyncDNSServer/AsyncDNSServer.ino deleted file mode 100644 index 88d0b83..0000000 --- a/pixelart-controller/lib/ESPAsyncDNSServer/examples/AsyncDNSServer/AsyncDNSServer.ino +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include - -const byte DNS_PORT = 53; -IPAddress apIP(192, 168, 1, 1); -AsyncDNSServer dnsServer; -ESP8266WebServer webServer(80); - -void setup() { - WiFi.mode(WIFI_AP); - WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); - WiFi.softAP("DNSServer example"); - - // modify TTL associated with the domain name (in seconds) - // default is 60 seconds - dnsServer.setTTL(300); - // set which return code will be used for all other domains (e.g. sending - // ServerFailure instead of NonExistentDomain will reduce number of queries - // sent by clients) - // default is AsyncDNSReplyCode::NonExistentDomain - dnsServer.setErrorReplyCode(AsyncDNSReplyCode::ServerFailure); - - // start DNS server for a specific domain name - dnsServer.start(DNS_PORT, "www.example.com", apIP); - - // simple HTTP server to see that DNS server is working - webServer.onNotFound([]() { - String message = "Hello World!\n\n"; - message += "URI: "; - message += webServer.uri(); - - webServer.send(200, "text/plain", message); - }); - webServer.begin(); -} - -void loop() { - webServer.handleClient(); -} diff --git a/pixelart-controller/lib/ESPAsyncDNSServer/examples/asyncDNServerFull/asyncDNServerFull.ino b/pixelart-controller/lib/ESPAsyncDNSServer/examples/asyncDNServerFull/asyncDNServerFull.ino deleted file mode 100644 index 508384d..0000000 --- a/pixelart-controller/lib/ESPAsyncDNSServer/examples/asyncDNServerFull/asyncDNServerFull.ino +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include - -// typedef enum { -// HTTP_GET = 0b00000001, -// HTTP_POST = 0b00000010, -// HTTP_DELETE = 0b00000100, -// HTTP_PUT = 0b00001000, -// HTTP_PATCH = 0b00010000, -// HTTP_HEAD = 0b00100000, -// HTTP_OPTIONS = 0b01000000, -// HTTP_ANY = 0b01111111, -// } WebRequestMethod; - -const byte DNS_PORT = 53; -IPAddress apIP(192, 168, 1, 1); -AsyncDNSServer dnsServer; -AsyncWebServer webServer(80); - -void setup() { - Serial.begin(115200); - WiFi.mode(WIFI_AP); - WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); - WiFi.softAP("DNSServer example"); - - // modify TTL associated with the domain name (in seconds) - // default is 60 seconds - dnsServer.setTTL(300); - // set which return code will be used for all other domains (e.g. sending - // ServerFailure instead of NonExistentDomain will reduce number of queries - // sent by clients) - // default is AsyncDNSReplyCode::NonExistentDomain - dnsServer.setErrorReplyCode(AsyncDNSReplyCode::ServerFailure); - - // start DNS server for a specific domain name - dnsServer.start(DNS_PORT, "*", apIP); - - // simple HTTP server to see that DNS server is working - webServer.on("/", 0b00000001, [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", String(ESP.getFreeHeap())); - }); - webServer.begin(); - Serial.println(F("setup done")); -} - -void loop() { -} diff --git a/pixelart-controller/lib/ESPAsyncDNSServer/library.json b/pixelart-controller/lib/ESPAsyncDNSServer/library.json deleted file mode 100644 index 2d162f9..0000000 --- a/pixelart-controller/lib/ESPAsyncDNSServer/library.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name":"ESPAsyncDNSServer", - "description":"Asynchronous DNS Server Library for ESP8266", - "keywords":"async,dns,server", - "authors": - { - "name": "Develo", - "maintainer": true - }, - "repository": - { - "type": "git", - "url": "https://github.com/devyte/ESPAsyncDNSServer.git" - }, - "version": "1.0.0", - "license": "LGPL-3.0", - "frameworks": "arduino", - "platforms":"espressif", - "examples": [ - "examples/*/*.ino" - ] -} diff --git a/pixelart-controller/lib/ESPAsyncDNSServer/library.properties b/pixelart-controller/lib/ESPAsyncDNSServer/library.properties deleted file mode 100644 index 7a7cf84..0000000 --- a/pixelart-controller/lib/ESPAsyncDNSServer/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=ESP AsyncDNSServer -version=1.0.0 -author=devyte -maintainer=devyte -sentence=Async DNS Server Library for ESP -paragraph=Async DNS Server Library for ESP -category=Other -url=https://github.com/devyte/ESPAsyncDNSServer.git -architectures=* diff --git a/pixelart-controller/lib/ESPAsyncDNSServer/src/ESPAsyncDNSServer.cpp b/pixelart-controller/lib/ESPAsyncDNSServer/src/ESPAsyncDNSServer.cpp deleted file mode 100644 index 5bf19c5..0000000 --- a/pixelart-controller/lib/ESPAsyncDNSServer/src/ESPAsyncDNSServer.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include "ESPAsyncDNSServer.h" -#include -#include - - - -namespace -{ - - -struct DNSHeader -{ - uint16_t ID; // identification number - unsigned char RD : 1; // recursion desired - unsigned char TC : 1; // truncated message - unsigned char AA : 1; // authoritive answer - unsigned char OPCode : 4; // message_type - unsigned char QR : 1; // query/response flag - unsigned char RCode : 4; // response code - unsigned char Z : 3; // its z! reserved - unsigned char RA : 1; // recursion available - uint16_t QDCount; // number of question entries - uint16_t ANCount; // number of answer entries - uint16_t NSCount; // number of authority entries - uint16_t ARCount; // number of resource entries -}; - - - - - - -bool -requestIncludesOnlyOneQuestion(DNSHeader * _dnsHeader) -{ - return ntohs(_dnsHeader->QDCount) == 1 && - _dnsHeader->ANCount == 0 && - _dnsHeader->NSCount == 0 && - _dnsHeader->ARCount == 0; -} - -void -downcaseAndRemoveWwwPrefix(String &domainName) -{ - domainName.toLowerCase(); - domainName.replace("www.", ""); -} - -String -getDomainNameWithoutWwwPrefix(unsigned char *start) -{ - String parsedDomainName = ""; - if (start == nullptr || *start == 0) - return parsedDomainName; - - int pos = 0; - while(true) - { - unsigned char labelLength = *(start + pos); - for(int i = 0; i < labelLength; i++) - { - pos++; - parsedDomainName += (char)*(start + pos); - } - pos++; - if (*(start + pos) == 0) - { - downcaseAndRemoveWwwPrefix(parsedDomainName); - return parsedDomainName; - } - else - { - parsedDomainName += "."; - } - } -} - - - -} - - - - - - - - -AsyncDNSServer::AsyncDNSServer() -{ - _ttl = htonl(60); - _errorReplyCode = AsyncDNSReplyCode::NonExistentDomain; -} - -bool -AsyncDNSServer::start(const uint16_t port, const String &domainName, - const IPAddress &resolvedIP) -{ - _port = port; - _domainName = domainName; - _resolvedIP[0] = resolvedIP[0]; - _resolvedIP[1] = resolvedIP[1]; - _resolvedIP[2] = resolvedIP[2]; - _resolvedIP[3] = resolvedIP[3]; - downcaseAndRemoveWwwPrefix(_domainName); - if(_udp.listen(_port)) - { - _udp.onPacket( - [&](AsyncUDPPacket &packet) - { - this->processRequest(packet); - } - ); - return true; - } - return false; -} - -void -AsyncDNSServer::setErrorReplyCode(const AsyncDNSReplyCode &replyCode) -{ - _errorReplyCode = replyCode; -} - -void -AsyncDNSServer::setTTL(const uint32_t ttl) -{ - _ttl = htonl(ttl); -} - -void -AsyncDNSServer::stop() -{ - _udp.close(); -} - -void -AsyncDNSServer::processRequest(AsyncUDPPacket &packet) -{ - if (packet.length() >= sizeof(DNSHeader)) - { - unsigned char * _buffer = packet.data(); - DNSHeader * _dnsHeader = (DNSHeader*) _buffer; - - String domainNameWithoutWwwPrefix = (_buffer == nullptr ? "" : getDomainNameWithoutWwwPrefix(_buffer + sizeof(DNSHeader))); - - if (_dnsHeader->QR == DNS_QR_QUERY && - _dnsHeader->OPCode == DNS_OPCODE_QUERY && - requestIncludesOnlyOneQuestion(_dnsHeader) && - (_domainName == "*" || domainNameWithoutWwwPrefix == _domainName) - ) - { - replyWithIP(packet); - } - else if (_dnsHeader->QR == DNS_QR_QUERY) - { - replyWithCustomCode(packet); - } - } -} - - - -void -AsyncDNSServer::replyWithIP(AsyncUDPPacket &packet) -{ - AsyncUDPMessage msg(packet.length() + 12 + sizeof(_resolvedIP)); //6 bytes below + szeof(ttl) + 2 bytes. Precalculate to avoid using default of 1460, which is way too much - - msg.write(packet.data(), packet.length()); - DNSHeader * _dnsHeader = (DNSHeader *)msg.data(); - - _dnsHeader->QR = DNS_QR_RESPONSE; - _dnsHeader->ANCount = _dnsHeader->QDCount; - _dnsHeader->QDCount = _dnsHeader->QDCount; - //_dnsHeader->RA = 1; - - msg.write((uint8_t)192); // answer name is a pointer - msg.write((uint8_t)12); // pointer to offset at 0x00c - - msg.write((uint8_t)0); // 0x0001 answer is type A query (host address) - msg.write((uint8_t)1); - - msg.write((uint8_t)0); //0x0001 answer is class IN (internet address) - msg.write((uint8_t)1); - - msg.write((uint8_t *)&_ttl, sizeof(_ttl)); - - // Length of RData is 4 bytes (because, in this case, RData is IPv4) - msg.write((uint8_t)0); - msg.write((uint8_t)4); - msg.write(_resolvedIP, sizeof(_resolvedIP)); - - packet.send(msg); - - - #ifdef DEBUG - DEBUG_OUTPUT.print("DNS responds: "); - DEBUG_OUTPUT.print(_resolvedIP[0]); - DEBUG_OUTPUT.print("."); - DEBUG_OUTPUT.print(_resolvedIP[1]); - DEBUG_OUTPUT.print("."); - DEBUG_OUTPUT.print(_resolvedIP[2]); - DEBUG_OUTPUT.print("."); - DEBUG_OUTPUT.print(_resolvedIP[3]); -// DEBUG_OUTPUT.print(" for "); -// DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix()); - #endif -} - -void -AsyncDNSServer::replyWithCustomCode(AsyncUDPPacket &packet) -{ - AsyncUDPMessage msg(packet.length()); - - msg.write(packet.data(), packet.length()); - DNSHeader * _dnsHeader = (DNSHeader *)msg.data(); - - _dnsHeader->QR = DNS_QR_RESPONSE; - _dnsHeader->RCode = (unsigned char)_errorReplyCode; //default is AsyncDNSReplyCode::NonExistentDomain - _dnsHeader->QDCount = 0; - - packet.send(msg); -} diff --git a/pixelart-controller/lib/ESPAsyncDNSServer/src/ESPAsyncDNSServer.h b/pixelart-controller/lib/ESPAsyncDNSServer/src/ESPAsyncDNSServer.h deleted file mode 100644 index d64e329..0000000 --- a/pixelart-controller/lib/ESPAsyncDNSServer/src/ESPAsyncDNSServer.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef ESPAsyncDNSServer_h -#define ESPAsyncDNSServer_h - -#ifdef ARDUINO_ARCH_ESP32 -#ifndef Stream_h -#include -#endif -#include -#else -#include -#endif - -#define DNS_QR_QUERY 0 -#define DNS_QR_RESPONSE 1 -#define DNS_OPCODE_QUERY 0 - -enum class AsyncDNSReplyCode : unsigned char -{ - NoError = 0, - FormError = 1, - ServerFailure = 2, - NonExistentDomain = 3, - NotImplemented = 4, - Refused = 5, - YXDomain = 6, - YXRRSet = 7, - NXRRSet = 8 -}; - -class AsyncDNSServer -{ - public: - AsyncDNSServer(); - void setErrorReplyCode(const AsyncDNSReplyCode &replyCode); - void setTTL(const uint32_t ttl); - - // Returns true if successful, false if there are no sockets available - bool start(const uint16_t port, - const String &domainName, - const IPAddress &resolvedIP); - // stops the DNS server - void stop(); - - private: - AsyncUDP _udp; - uint16_t _port; - String _domainName; - unsigned char _resolvedIP[4]; - uint32_t _ttl; - AsyncDNSReplyCode _errorReplyCode; - - void processRequest(AsyncUDPPacket &packet); - void replyWithIP(AsyncUDPPacket &packet); - void replyWithCustomCode(AsyncUDPPacket &packet); -}; - - -#endif diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp32.sh b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp32.sh new file mode 100644 index 0000000..cf1026d --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp32.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +export ARDUINO_ESP32_PATH="$ARDUINO_USR_PATH/hardware/espressif/esp32" +if [ ! -d "$ARDUINO_ESP32_PATH" ]; then + echo "Installing ESP32 Arduino Core ..." + script_init_path="$PWD" + mkdir -p "$ARDUINO_USR_PATH/hardware/espressif" + cd "$ARDUINO_USR_PATH/hardware/espressif" + + echo "Installing Python Serial ..." + pip install pyserial > /dev/null + + if [ "$OS_IS_WINDOWS" == "1" ]; then + echo "Installing Python Requests ..." + pip install requests > /dev/null + fi + + if [ "$GITHUB_REPOSITORY" == "espressif/arduino-esp32" ]; then + echo "Linking Core..." + ln -s $GITHUB_WORKSPACE esp32 + else + echo "Cloning Core Repository..." + git clone https://github.com/espressif/arduino-esp32.git esp32 > /dev/null 2>&1 + fi + + echo "Updating Submodules ..." + cd esp32 + git submodule update --init --recursive > /dev/null 2>&1 + + echo "Installing Platform Tools ..." + cd tools && python get.py + cd $script_init_path + + echo "ESP32 Arduino has been installed in '$ARDUINO_ESP32_PATH'" + echo "" +fi diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp8266.sh b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp8266.sh new file mode 100644 index 0000000..048cd02 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp8266.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "Installing ESP8266 Arduino Core ..." +script_init_path="$PWD" +mkdir -p "$ARDUINO_USR_PATH/hardware/esp8266com" +cd "$ARDUINO_USR_PATH/hardware/esp8266com" + +echo "Installing Python Serial ..." +pip install pyserial > /dev/null + +if [ "$OS_IS_WINDOWS" == "1" ]; then + echo "Installing Python Requests ..." + pip install requests > /dev/null +fi + +echo "Cloning Core Repository ..." +git clone https://github.com/esp8266/Arduino.git esp8266 > /dev/null 2>&1 + +echo "Updating submodules ..." +cd esp8266 +git submodule update --init --recursive > /dev/null 2>&1 + +echo "Installing Platform Tools ..." +cd tools +python get.py > /dev/null +cd $script_init_path + +echo "ESP8266 Arduino has been installed in '$ARDUINO_USR_PATH/hardware/esp8266com'" +echo "" diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-ide.sh b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-ide.sh new file mode 100644 index 0000000..ce60cb8 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-arduino-ide.sh @@ -0,0 +1,228 @@ +#!/bin/bash + +#OSTYPE: 'linux-gnu', ARCH: 'x86_64' => linux64 +#OSTYPE: 'msys', ARCH: 'x86_64' => win32 +#OSTYPE: 'darwin18', ARCH: 'i386' => macos + +OSBITS=`arch` +if [[ "$OSTYPE" == "linux"* ]]; then + export OS_IS_LINUX="1" + ARCHIVE_FORMAT="tar.xz" + if [[ "$OSBITS" == "i686" ]]; then + OS_NAME="linux32" + elif [[ "$OSBITS" == "x86_64" ]]; then + OS_NAME="linux64" + elif [[ "$OSBITS" == "armv7l" || "$OSBITS" == "aarch64" ]]; then + OS_NAME="linuxarm" + else + OS_NAME="$OSTYPE-$OSBITS" + echo "Unknown OS '$OS_NAME'" + exit 1 + fi +elif [[ "$OSTYPE" == "darwin"* ]]; then + export OS_IS_MACOS="1" + ARCHIVE_FORMAT="zip" + OS_NAME="macosx" +elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then + export OS_IS_WINDOWS="1" + ARCHIVE_FORMAT="zip" + OS_NAME="windows" +else + OS_NAME="$OSTYPE-$OSBITS" + echo "Unknown OS '$OS_NAME'" + exit 1 +fi +export OS_NAME + +ARDUINO_BUILD_DIR="$HOME/.arduino/build.tmp" +ARDUINO_CACHE_DIR="$HOME/.arduino/cache.tmp" + +if [ "$OS_IS_MACOS" == "1" ]; then + export ARDUINO_IDE_PATH="/Applications/Arduino.app/Contents/Java" + export ARDUINO_USR_PATH="$HOME/Documents/Arduino" +elif [ "$OS_IS_WINDOWS" == "1" ]; then + export ARDUINO_IDE_PATH="$HOME/arduino_ide" + export ARDUINO_USR_PATH="$HOME/Documents/Arduino" +else + export ARDUINO_IDE_PATH="$HOME/arduino_ide" + export ARDUINO_USR_PATH="$HOME/Arduino" +fi + +if [ ! -d "$ARDUINO_IDE_PATH" ]; then + echo "Installing Arduino IDE on $OS_NAME ..." + echo "Downloading 'arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT' to 'arduino.$ARCHIVE_FORMAT' ..." + if [ "$OS_IS_LINUX" == "1" ]; then + wget -O "arduino.$ARCHIVE_FORMAT" "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 + echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." + tar xf "arduino.$ARCHIVE_FORMAT" > /dev/null + mv arduino-nightly "$ARDUINO_IDE_PATH" + else + curl -o "arduino.$ARCHIVE_FORMAT" -L "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 + echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." + unzip "arduino.$ARCHIVE_FORMAT" > /dev/null + if [ "$OS_IS_MACOS" == "1" ]; then + mv "Arduino.app" "/Applications/Arduino.app" + else + mv arduino-nightly "$ARDUINO_IDE_PATH" + fi + fi + rm -rf "arduino.$ARCHIVE_FORMAT" + + mkdir -p "$ARDUINO_USR_PATH/libraries" + mkdir -p "$ARDUINO_USR_PATH/hardware" + + echo "Arduino IDE Installed in '$ARDUINO_IDE_PATH'" + echo "" +fi + +function build_sketch(){ # build_sketch [extra-options] + if [ "$#" -lt 2 ]; then + echo "ERROR: Illegal number of parameters" + echo "USAGE: build_sketch [extra-options]" + return 1 + fi + + local fqbn="$1" + local sketch="$2" + local build_flags="$3" + local xtra_opts="$4" + local win_opts="" + if [ "$OS_IS_WINDOWS" == "1" ]; then + local ctags_version=`ls "$ARDUINO_IDE_PATH/tools-builder/ctags/"` + local preprocessor_version=`ls "$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/"` + win_opts="-prefs=runtime.tools.ctags.path=$ARDUINO_IDE_PATH/tools-builder/ctags/$ctags_version -prefs=runtime.tools.arduino-preprocessor.path=$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/$preprocessor_version" + fi + + echo "" + echo "Compiling '"$(basename "$sketch")"' ..." + mkdir -p "$ARDUINO_BUILD_DIR" + mkdir -p "$ARDUINO_CACHE_DIR" + $ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 \ + -fqbn=$fqbn \ + -warnings="all" \ + -tools "$ARDUINO_IDE_PATH/tools-builder" \ + -tools "$ARDUINO_IDE_PATH/tools" \ + -built-in-libraries "$ARDUINO_IDE_PATH/libraries" \ + -hardware "$ARDUINO_IDE_PATH/hardware" \ + -hardware "$ARDUINO_USR_PATH/hardware" \ + -libraries "$ARDUINO_USR_PATH/libraries" \ + -build-cache "$ARDUINO_CACHE_DIR" \ + -build-path "$ARDUINO_BUILD_DIR" \ + -prefs=compiler.cpp.extra_flags="$build_flags" \ + $win_opts $xtra_opts "$sketch" +} + +function count_sketches() # count_sketches +{ + local examples="$1" + rm -rf sketches.txt + if [ ! -d "$examples" ]; then + touch sketches.txt + return 0 + fi + local sketches=$(find $examples -name *.ino) + local sketchnum=0 + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then + continue + fi; + if [[ -f "$sketchdir/.test.skip" ]]; then + continue + fi + echo $sketch >> sketches.txt + sketchnum=$(($sketchnum + 1)) + done + return $sketchnum +} + +function build_sketches() # build_sketches [extra-options] +{ + local fqbn=$1 + local examples=$2 + local chunk_idex=$3 + local chunks_num=$4 + local xtra_opts=$5 + + if [ "$#" -lt 2 ]; then + echo "ERROR: Illegal number of parameters" + echo "USAGE: build_sketches [ ] [extra-options]" + return 1 + fi + + if [ "$#" -lt 4 ]; then + chunk_idex="0" + chunks_num="1" + xtra_opts=$3 + fi + + if [ "$chunks_num" -le 0 ]; then + echo "ERROR: Chunks count must be positive number" + return 1 + fi + if [ "$chunk_idex" -ge "$chunks_num" ]; then + echo "ERROR: Chunk index must be less than chunks count" + return 1 + fi + + set +e + count_sketches "$examples" + local sketchcount=$? + set -e + local sketches=$(cat sketches.txt) + rm -rf sketches.txt + + local chunk_size=$(( $sketchcount / $chunks_num )) + local all_chunks=$(( $chunks_num * $chunk_size )) + if [ "$all_chunks" -lt "$sketchcount" ]; then + chunk_size=$(( $chunk_size + 1 )) + fi + + local start_index=$(( $chunk_idex * $chunk_size )) + if [ "$sketchcount" -le "$start_index" ]; then + echo "Skipping job" + return 0 + fi + + local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) + if [ "$end_index" -gt "$sketchcount" ]; then + end_index=$sketchcount + fi + + local start_num=$(( $start_index + 1 )) + echo "Found $sketchcount Sketches"; + echo "Chunk Count : $chunks_num" + echo "Chunk Size : $chunk_size" + echo "Start Sketch: $start_num" + echo "End Sketch : $end_index" + + local sketchnum=0 + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [ "${sketchdirname}.ino" != "$sketchname" ] \ + || [ -f "$sketchdir/.test.skip" ]; then + continue + fi + sketchnum=$(($sketchnum + 1)) + if [ "$sketchnum" -le "$start_index" ] \ + || [ "$sketchnum" -gt "$end_index" ]; then + continue + fi + local sketchBuildFlags="" + if [ -f "$sketchdir/.test.build_flags" ]; then + while read line; do + sketchBuildFlags="$sketchBuildFlags $line" + done < "$sketchdir/.test.build_flags" + fi + build_sketch "$fqbn" "$sketch" "$sketchBuildFlags" "$xtra_opts" + local result=$? + if [ $result -ne 0 ]; then + return $result + fi + done + return 0 +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-platformio.sh b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-platformio.sh new file mode 100644 index 0000000..594948e --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/install-platformio.sh @@ -0,0 +1,140 @@ +#!/bin/bash + +echo "Installing Python Wheel ..." +pip install wheel > /dev/null 2>&1 + +echo "Installing PlatformIO ..." +pip install -U platformio > /dev/null 2>&1 + +echo "PlatformIO has been installed" +echo "" + + +function build_pio_sketch(){ # build_pio_sketch + if [ "$#" -lt 3 ]; then + echo "ERROR: Illegal number of parameters" + echo "USAGE: build_pio_sketch " + return 1 + fi + + local board="$1" + local sketch="$2" + local buildFlags="$3" + local sketch_dir=$(dirname "$sketch") + echo "" + echo "Compiling '"$(basename "$sketch")"' ..." + python -m platformio ci -l '.' --board "$board" "$sketch_dir" --project-option="board_build.partitions = huge_app.csv" --project-option="build_flags=$buildFlags" +} + +function count_sketches() # count_sketches +{ + local examples="$1" + rm -rf sketches.txt + if [ ! -d "$examples" ]; then + touch sketches.txt + return 0 + fi + local sketches=$(find $examples -name *.ino) + local sketchnum=0 + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then + continue + fi; + if [[ -f "$sketchdir/.test.skip" ]]; then + continue + fi + echo $sketch >> sketches.txt + sketchnum=$(($sketchnum + 1)) + done + return $sketchnum +} + +function build_pio_sketches() # build_pio_sketches +{ + if [ "$#" -lt 2 ]; then + echo "ERROR: Illegal number of parameters" + echo "USAGE: build_pio_sketches [ ]" + return 1 + fi + + local board=$1 + local examples=$2 + local chunk_idex=$3 + local chunks_num=$4 + + if [ "$#" -lt 4 ]; then + chunk_idex="0" + chunks_num="1" + fi + + if [ "$chunks_num" -le 0 ]; then + echo "ERROR: Chunks count must be positive number" + return 1 + fi + if [ "$chunk_idex" -ge "$chunks_num" ]; then + echo "ERROR: Chunk index must be less than chunks count" + return 1 + fi + + set +e + count_sketches "$examples" + local sketchcount=$? + set -e + local sketches=$(cat sketches.txt) + rm -rf sketches.txt + + local chunk_size=$(( $sketchcount / $chunks_num )) + local all_chunks=$(( $chunks_num * $chunk_size )) + if [ "$all_chunks" -lt "$sketchcount" ]; then + chunk_size=$(( $chunk_size + 1 )) + fi + + local start_index=$(( $chunk_idex * $chunk_size )) + if [ "$sketchcount" -le "$start_index" ]; then + echo "Skipping job" + return 0 + fi + + local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) + if [ "$end_index" -gt "$sketchcount" ]; then + end_index=$sketchcount + fi + + local start_num=$(( $start_index + 1 )) + echo "Found $sketchcount Sketches"; + echo "Chunk Count : $chunks_num" + echo "Chunk Size : $chunk_size" + echo "Start Sketch: $start_num" + echo "End Sketch : $end_index" + + local sketchnum=0 + for sketch in $sketches; do + local sketchdir=$(dirname $sketch) + local sketchdirname=$(basename $sketchdir) + local sketchname=$(basename $sketch) + if [ "${sketchdirname}.ino" != "$sketchname" ] \ + || [ -f "$sketchdir/.test.skip" ]; then + continue + fi + local sketchBuildFlags="" + if [ -f "$sketchdir/.test.build_flags" ]; then + while read line; do + sketchBuildFlags="$sketchBuildFlags $line" + done < "$sketchdir/.test.build_flags" + fi + sketchnum=$(($sketchnum + 1)) + if [ "$sketchnum" -le "$start_index" ] \ + || [ "$sketchnum" -gt "$end_index" ]; then + continue + fi + build_pio_sketch "$board" "$sketch" "$sketchBuildFlags" + local result=$? + if [ $result -ne 0 ]; then + return $result + fi + done + return 0 +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/on-push.sh b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/on-push.sh new file mode 100644 index 0000000..96fff57 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.github/scripts/on-push.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +set -e + +if [ ! -z "$TRAVIS_BUILD_DIR" ]; then + export GITHUB_WORKSPACE="$TRAVIS_BUILD_DIR" + export GITHUB_REPOSITORY="$TRAVIS_REPO_SLUG" +elif [ -z "$GITHUB_WORKSPACE" ]; then + export GITHUB_WORKSPACE="$PWD" + export GITHUB_REPOSITORY="me-no-dev/ESPAsyncWebServer" +fi + +TARGET_PLATFORM="$1" +CHUNK_INDEX=$2 +CHUNKS_CNT=$3 +BUILD_PIO=0 +if [ "$#" -lt 1 ]; then + TARGET_PLATFORM="esp32" +fi +if [ "$#" -lt 3 ] || [ "$CHUNKS_CNT" -le 0 ]; then + CHUNK_INDEX=0 + CHUNKS_CNT=1 +elif [ "$CHUNK_INDEX" -gt "$CHUNKS_CNT" ]; then + CHUNK_INDEX=$CHUNKS_CNT +elif [ "$CHUNK_INDEX" -eq "$CHUNKS_CNT" ]; then + BUILD_PIO=1 +fi + +if [ "$BUILD_PIO" -eq 0 ]; then + # ArduinoIDE Test + source ./.github/scripts/install-arduino-ide.sh + + echo "Installing ESPAsyncWebServer ..." + cp -rf "$GITHUB_WORKSPACE" "$ARDUINO_USR_PATH/libraries/ESPAsyncWebServer" + echo "Installing ArduinoJson ..." + git clone https://github.com/bblanchon/ArduinoJson "$ARDUINO_USR_PATH/libraries/ArduinoJson" > /dev/null 2>&1 + + if [[ "$TARGET_PLATFORM" == "esp32" ]]; then + echo "Installing AsyncTCP ..." + git clone https://github.com/me-no-dev/AsyncTCP "$ARDUINO_USR_PATH/libraries/AsyncTCP" > /dev/null 2>&1 + FQBN="espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app" + source ./.github/scripts/install-arduino-core-esp32.sh + echo "BUILDING ESP32 EXAMPLES" + else + echo "Installing ESPAsyncTCP ..." + git clone https://github.com/me-no-dev/ESPAsyncTCP "$ARDUINO_USR_PATH/libraries/ESPAsyncTCP" > /dev/null 2>&1 + FQBN="esp8266com:esp8266:generic:eesz=4M1M,ip=lm2f" + source ./.github/scripts/install-arduino-core-esp8266.sh + echo "BUILDING ESP8266 EXAMPLES" + fi + build_sketches "$FQBN" "$GITHUB_WORKSPACE/examples" "$CHUNK_INDEX" "$CHUNKS_CNT" +else + # PlatformIO Test + source ./.github/scripts/install-platformio.sh + + python -m platformio lib --storage-dir "$GITHUB_WORKSPACE" install + echo "Installing ArduinoJson ..." + python -m platformio lib -g install https://github.com/bblanchon/ArduinoJson.git > /dev/null 2>&1 + if [[ "$TARGET_PLATFORM" == "esp32" ]]; then + BOARD="esp32dev" + echo "Installing AsyncTCP ..." + python -m platformio lib -g install https://github.com/me-no-dev/AsyncTCP.git > /dev/null 2>&1 + echo "BUILDING ESP32 EXAMPLES" + else + BOARD="esp12e" + echo "Installing ESPAsyncTCP ..." + python -m platformio lib -g install https://github.com/me-no-dev/ESPAsyncTCP.git > /dev/null 2>&1 + echo "BUILDING ESP8266 EXAMPLES" + fi + build_pio_sketches "$BOARD" "$GITHUB_WORKSPACE/examples" +fi diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.github/stale.yml b/pixelart-controller/lib/ESPAsyncWebServer/.github/stale.yml new file mode 100644 index 0000000..ce7a8e3 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.github/stale.yml @@ -0,0 +1,31 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +daysUntilStale: 60 +daysUntilClose: 14 +limitPerRun: 30 +staleLabel: stale +exemptLabels: + - pinned + - security + - "to be implemented" + - "for reference" + - "move to PR" + - "enhancement" + +only: issues +onlyLabels: [] +exemptProjects: false +exemptMilestones: false +exemptAssignees: false + +markComment: > + [STALE_SET] This issue has been automatically marked as stale because it has not had + recent activity. It will be closed in 14 days if no further activity occurs. Thank you + for your contributions. + +unmarkComment: > + [STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it openin the future. + +closeComment: > + [STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions. + diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.github/workflows/push.yml b/pixelart-controller/lib/ESPAsyncWebServer/.github/workflows/push.yml new file mode 100644 index 0000000..231dc52 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.github/workflows/push.yml @@ -0,0 +1,34 @@ +name: ESP Async Web Server CI + +on: + push: + branches: + - master + - release/* + pull_request: + +jobs: + + build-arduino: + name: Arduino for ${{ matrix.board }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + board: [esp32, esp8266] + steps: + - uses: actions/checkout@v1 + - name: Build Tests + run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 0 1 + +# build-pio: +# name: PlatformIO for ${{ matrix.board }} on ${{ matrix.os }} +# runs-on: ${{ matrix.os }} +# strategy: +# matrix: +# os: [ubuntu-latest, windows-latest, macOS-latest] +# board: [esp32, esp8266] +# steps: +# - uses: actions/checkout@v1 +# - name: Build Tests +# run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 1 1 diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.gitignore b/pixelart-controller/lib/ESPAsyncWebServer/.gitignore new file mode 100644 index 0000000..a0f0e53 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.gitignore @@ -0,0 +1,2 @@ +.vscode +.DS_Store diff --git a/pixelart-controller/lib/ESPAsyncWebServer/.travis.yml b/pixelart-controller/lib/ESPAsyncWebServer/.travis.yml new file mode 100644 index 0000000..e1b7035 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/.travis.yml @@ -0,0 +1,46 @@ +sudo: false + +language: python + +os: + - linux + +git: + depth: false + +stages: + - build + +jobs: + include: + + - name: "Build Arduino ESP32" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 + + - name: "Build Arduino ESP8266" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 + + - name: "Build Platformio ESP32" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 1 1 + + - name: "Build Platformio ESP8266" + if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) + stage: build + script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 1 1 + +notifications: + email: + on_success: change + on_failure: change + webhooks: + urls: + - https://webhooks.gitter.im/e/60e65d0c78ea0a920347 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always diff --git a/pixelart-controller/lib/ESPAsyncWebServer/CMakeLists.txt b/pixelart-controller/lib/ESPAsyncWebServer/CMakeLists.txt new file mode 100644 index 0000000..64292ec --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/CMakeLists.txt @@ -0,0 +1,17 @@ +set(COMPONENT_SRCDIRS + "src" +) + +set(COMPONENT_ADD_INCLUDEDIRS + "src" +) + +set(COMPONENT_REQUIRES + "arduino-esp32" + "AsyncTCP" +) + +register_component() + +target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32) +target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti) diff --git a/pixelart-controller/lib/ESPAsyncWebServer/README.md b/pixelart-controller/lib/ESPAsyncWebServer/README.md new file mode 100644 index 0000000..d6dd320 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/README.md @@ -0,0 +1,1521 @@ +# ESPAsyncWebServer +[![Build Status](https://travis-ci.org/me-no-dev/ESPAsyncWebServer.svg?branch=master)](https://travis-ci.org/me-no-dev/ESPAsyncWebServer) ![](https://github.com/me-no-dev/ESPAsyncWebServer/workflows/ESP%20Async%20Web%20Server%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/395dd42cfc674e6ca2e326af3af80ffc)](https://www.codacy.com/manual/me-no-dev/ESPAsyncWebServer?utm_source=github.com&utm_medium=referral&utm_content=me-no-dev/ESPAsyncWebServer&utm_campaign=Badge_Grade) + +For help and support [![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +Async HTTP and WebSocket Server for ESP8266 Arduino + +For ESP8266 it requires [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) +To use this library you might need to have the latest git versions of [ESP8266](https://github.com/esp8266/Arduino) Arduino Core + +For ESP32 it requires [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) to work +To use this library you might need to have the latest git versions of [ESP32](https://github.com/espressif/arduino-esp32) Arduino Core + +## Table of contents +- [ESPAsyncWebServer](#espasyncwebserver) + - [Table of contents](#table-of-contents) + - [Installation](#installation) + - [Using PlatformIO](#using-platformio) + - [Why should you care](#why-should-you-care) + - [Important things to remember](#important-things-to-remember) + - [Principles of operation](#principles-of-operation) + - [The Async Web server](#the-async-web-server) + - [Request Life Cycle](#request-life-cycle) + - [Rewrites and how do they work](#rewrites-and-how-do-they-work) + - [Handlers and how do they work](#handlers-and-how-do-they-work) + - [Responses and how do they work](#responses-and-how-do-they-work) + - [Template processing](#template-processing) + - [Libraries and projects that use AsyncWebServer](#libraries-and-projects-that-use-asyncwebserver) + - [Request Variables](#request-variables) + - [Common Variables](#common-variables) + - [Headers](#headers) + - [GET, POST and FILE parameters](#get-post-and-file-parameters) + - [FILE Upload handling](#file-upload-handling) + - [Body data handling](#body-data-handling) + - [JSON body handling with ArduinoJson](#json-body-handling-with-arduinojson) + - [Responses](#responses) + - [Redirect to another URL](#redirect-to-another-url) + - [Basic response with HTTP Code](#basic-response-with-http-code) + - [Basic response with HTTP Code and extra headers](#basic-response-with-http-code-and-extra-headers) + - [Basic response with string content](#basic-response-with-string-content) + - [Basic response with string content and extra headers](#basic-response-with-string-content-and-extra-headers) + - [Send large webpage from PROGMEM](#send-large-webpage-from-progmem) + - [Send large webpage from PROGMEM and extra headers](#send-large-webpage-from-progmem-and-extra-headers) + - [Send large webpage from PROGMEM containing templates](#send-large-webpage-from-progmem-containing-templates) + - [Send large webpage from PROGMEM containing templates and extra headers](#send-large-webpage-from-progmem-containing-templates-and-extra-headers) + - [Send binary content from PROGMEM](#send-binary-content-from-progmem) + - [Respond with content coming from a Stream](#respond-with-content-coming-from-a-stream) + - [Respond with content coming from a Stream and extra headers](#respond-with-content-coming-from-a-stream-and-extra-headers) + - [Respond with content coming from a Stream containing templates](#respond-with-content-coming-from-a-stream-containing-templates) + - [Respond with content coming from a Stream containing templates and extra headers](#respond-with-content-coming-from-a-stream-containing-templates-and-extra-headers) + - [Respond with content coming from a File](#respond-with-content-coming-from-a-file) + - [Respond with content coming from a File and extra headers](#respond-with-content-coming-from-a-file-and-extra-headers) + - [Respond with content coming from a File containing templates](#respond-with-content-coming-from-a-file-containing-templates) + - [Respond with content using a callback](#respond-with-content-using-a-callback) + - [Respond with content using a callback and extra headers](#respond-with-content-using-a-callback-and-extra-headers) + - [Respond with content using a callback containing templates](#respond-with-content-using-a-callback-containing-templates) + - [Respond with content using a callback containing templates and extra headers](#respond-with-content-using-a-callback-containing-templates-and-extra-headers) + - [Chunked Response](#chunked-response) + - [Chunked Response containing templates](#chunked-response-containing-templates) + - [Print to response](#print-to-response) + - [ArduinoJson Basic Response](#arduinojson-basic-response) + - [ArduinoJson Advanced Response](#arduinojson-advanced-response) + - [Serving static files](#serving-static-files) + - [Serving specific file by name](#serving-specific-file-by-name) + - [Serving files in directory](#serving-files-in-directory) + - [Serving static files with authentication](#serving-static-files-with-authentication) + - [Specifying Cache-Control header](#specifying-cache-control-header) + - [Specifying Date-Modified header](#specifying-date-modified-header) + - [Specifying Template Processor callback](#specifying-template-processor-callback) + - [Param Rewrite With Matching](#param-rewrite-with-matching) + - [Using filters](#using-filters) + - [Serve different site files in AP mode](#serve-different-site-files-in-ap-mode) + - [Rewrite to different index on AP](#rewrite-to-different-index-on-ap) + - [Serving different hosts](#serving-different-hosts) + - [Determine interface inside callbacks](#determine-interface-inside-callbacks) + - [Bad Responses](#bad-responses) + - [Respond with content using a callback without content length to HTTP/1.0 clients](#respond-with-content-using-a-callback-without-content-length-to-http10-clients) + - [Async WebSocket Plugin](#async-websocket-plugin) + - [Async WebSocket Event](#async-websocket-event) + - [Methods for sending data to a socket client](#methods-for-sending-data-to-a-socket-client) + - [Direct access to web socket message buffer](#direct-access-to-web-socket-message-buffer) + - [Limiting the number of web socket clients](#limiting-the-number-of-web-socket-clients) + - [Async Event Source Plugin](#async-event-source-plugin) + - [Setup Event Source on the server](#setup-event-source-on-the-server) + - [Setup Event Source in the browser](#setup-event-source-in-the-browser) + - [Scanning for available WiFi Networks](#scanning-for-available-wifi-networks) + - [Remove handlers and rewrites](#remove-handlers-and-rewrites) + - [Setting up the server](#setting-up-the-server) + - [Setup global and class functions as request handlers](#setup-global-and-class-functions-as-request-handlers) + - [Methods for controlling websocket connections](#methods-for-controlling-websocket-connections) + - [Adding Default Headers](#adding-default-headers) + - [Path variable](#path-variable) + +## Installation + +### Using PlatformIO + +[PlatformIO](http://platformio.org) is an open source ecosystem for IoT development with cross platform build system, library manager and full support for Espressif ESP8266/ESP32 development. It works on the popular host OS: Mac OS X, Windows, Linux 32/64, Linux ARM (like Raspberry Pi, BeagleBone, CubieBoard). + +1. Install [PlatformIO IDE](http://platformio.org/platformio-ide) +2. Create new project using "PlatformIO Home > New Project" +3. Update dev/platform to staging version: + - [Instruction for Espressif 8266](http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version) + - [Instruction for Espressif 32](http://docs.platformio.org/en/latest/platforms/espressif32.html#using-arduino-framework-with-staging-version) + 4. Add "ESP Async WebServer" to project using [Project Configuration File `platformio.ini`](http://docs.platformio.org/page/projectconf.html) and [lib_deps](http://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps) option: + +```ini +[env:myboard] +platform = espressif... +board = ... +framework = arduino + +# using the latest stable version +lib_deps = ESP Async WebServer + +# or using GIT Url (the latest development version) +lib_deps = https://github.com/me-no-dev/ESPAsyncWebServer.git +``` + 5. Happy coding with PlatformIO! + +## Why should you care +- Using asynchronous network means that you can handle more than one connection at the same time +- You are called once the request is ready and parsed +- When you send the response, you are immediately ready to handle other connections + while the server is taking care of sending the response in the background +- Speed is OMG +- Easy to use API, HTTP Basic and Digest MD5 Authentication (default), ChunkedResponse +- Easily extendible to handle any type of content +- Supports Continue 100 +- Async WebSocket plugin offering different locations without extra servers or ports +- Async EventSource (Server-Sent Events) plugin to send events to the browser +- URL Rewrite plugin for conditional and permanent url rewrites +- ServeStatic plugin that supports cache, Last-Modified, default index and more +- Simple template processing engine to handle templates + +## Important things to remember +- This is fully asynchronous server and as such does not run on the loop thread. +- You can not use yield or delay or any function that uses them inside the callbacks +- The server is smart enough to know when to close the connection and free resources +- You can not send more than one response to a single request + +## Principles of operation + +### The Async Web server +- Listens for connections +- Wraps the new clients into ```Request``` +- Keeps track of clients and cleans memory +- Manages ```Rewrites``` and apply them on the request url +- Manages ```Handlers``` and attaches them to Requests + +### Request Life Cycle +- TCP connection is received by the server +- The connection is wrapped inside ```Request``` object +- When the request head is received (type, url, get params, http version and host), + the server goes through all ```Rewrites``` (in the order they were added) to rewrite the url and inject query parameters, + next, it goes through all attached ```Handlers```(in the order they were added) trying to find one + that ```canHandle``` the given request. If none are found, the default(catch-all) handler is attached. +- The rest of the request is received, calling the ```handleUpload``` or ```handleBody``` methods of the ```Handler``` if they are needed (POST+File/Body) +- When the whole request is parsed, the result is given to the ```handleRequest``` method of the ```Handler``` and is ready to be responded to +- In the ```handleRequest``` method, to the ```Request``` is attached a ```Response``` object (see below) that will serve the response data back to the client +- When the ```Response``` is sent, the client is closed and freed from the memory + +### Rewrites and how do they work +- The ```Rewrites``` are used to rewrite the request url and/or inject get parameters for a specific request url path. +- All ```Rewrites``` are evaluated on the request in the order they have been added to the server. +- The ```Rewrite``` will change the request url only if the request url (excluding get parameters) is fully match + the rewrite url, and when the optional ```Filter``` callback return true. +- Setting a ```Filter``` to the ```Rewrite``` enables to control when to apply the rewrite, decision can be based on + request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP. +- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface, + ```ON_STA_FILTER``` to execute the rewrite when request is made to the STA interface. +- The ```Rewrite``` can specify a target url with optional get parameters, e.g. ```/to-url?with=params``` + +### Handlers and how do they work +- The ```Handlers``` are used for executing specific actions to particular requests +- One ```Handler``` instance can be attached to any request and lives together with the server +- Setting a ```Filter``` to the ```Handler``` enables to control when to apply the handler, decision can be based on + request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP. +- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface, + ```ON_STA_FILTER``` to execute the rewrite when request is made to the STA interface. +- The ```canHandle``` method is used for handler specific control on whether the requests can be handled + and for declaring any interesting headers that the ```Request``` should parse. Decision can be based on request + method, request url, http version, request host/port/target host and get parameters +- Once a ```Handler``` is attached to given ```Request``` (```canHandle``` returned true) + that ```Handler``` takes care to receive any file/data upload and attach a ```Response``` + once the ```Request``` has been fully parsed +- ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only + if the ```Filter``` that was set to the ```Handler``` return true. +- The first ```Handler``` that can handle the request is selected, not further ```Filter``` and ```canHandle``` are called. + +### Responses and how do they work +- The ```Response``` objects are used to send the response data back to the client +- The ```Response``` object lives with the ```Request``` and is freed on end or disconnect +- Different techniques are used depending on the response type to send the data in packets + returning back almost immediately and sending the next packet when this one is received. + Any time in between is spent to run the user loop and handle other network packets +- Responding asynchronously is probably the most difficult thing for most to understand +- Many different options exist for the user to make responding a background task + +### Template processing +- ESPAsyncWebserver contains simple template processing engine. +- Template processing can be added to most response types. +- Currently it supports only replacing template placeholders with actual values. No conditional processing, cycles, etc. +- Placeholders are delimited with ```%``` symbols. Like this: ```%TEMPLATE_PLACEHOLDER%```. +- It works by extracting placeholder name from response text and passing it to user provided function which should return actual value to be used instead of placeholder. +- Since it's user provided function, it is possible for library users to implement conditional processing and cycles themselves. +- Since it's impossible to know the actual response size after template processing step in advance (and, therefore, to include it in response headers), the response becomes [chunked](#chunked-response). + +## Libraries and projects that use AsyncWebServer +- [WebSocketToSerial](https://github.com/hallard/WebSocketToSerial) - Debug serial devices through the web browser +- [Sattrack](https://github.com/Hopperpop/Sattrack) - Track the ISS with ESP8266 +- [ESP Radio](https://github.com/Edzelf/Esp-radio) - Icecast radio based on ESP8266 and VS1053 +- [VZero](https://github.com/andig/vzero) - the Wireless zero-config controller for volkszaehler.org +- [ESPurna](https://bitbucket.org/xoseperez/espurna) - ESPurna ("spark" in Catalan) is a custom C firmware for ESP8266 based smart switches. It was originally developed with the ITead Sonoff in mind. +- [fauxmoESP](https://bitbucket.org/xoseperez/fauxmoesp) - Belkin WeMo emulator library for ESP8266. +- [ESP-RFID](https://github.com/omersiar/esp-rfid) - MFRC522 RFID Access Control Management project for ESP8266. + +## Request Variables + +### Common Variables +```cpp +request->version(); // uint8_t: 0 = HTTP/1.0, 1 = HTTP/1.1 +request->method(); // enum: HTTP_GET, HTTP_POST, HTTP_DELETE, HTTP_PUT, HTTP_PATCH, HTTP_HEAD, HTTP_OPTIONS +request->url(); // String: URL of the request (not including host, port or GET parameters) +request->host(); // String: The requested host (can be used for virtual hosting) +request->contentType(); // String: ContentType of the request (not avaiable in Handler::canHandle) +request->contentLength(); // size_t: ContentLength of the request (not avaiable in Handler::canHandle) +request->multipart(); // bool: True if the request has content type "multipart" +``` + +### Headers +```cpp +//List all collected headers +int headers = request->headers(); +int i; +for(i=0;igetHeader(i); + Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); +} + +//get specific header by name +if(request->hasHeader("MyHeader")){ + AsyncWebHeader* h = request->getHeader("MyHeader"); + Serial.printf("MyHeader: %s\n", h->value().c_str()); +} + +//List all collected headers (Compatibility) +int headers = request->headers(); +int i; +for(i=0;iheaderName(i).c_str(), request->header(i).c_str()); +} + +//get specific header by name (Compatibility) +if(request->hasHeader("MyHeader")){ + Serial.printf("MyHeader: %s\n", request->header("MyHeader").c_str()); +} +``` + +### GET, POST and FILE parameters +```cpp +//List all parameters +int params = request->params(); +for(int i=0;igetParam(i); + if(p->isFile()){ //p->isPost() is also true + Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); + } else if(p->isPost()){ + Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } else { + Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } +} + +//Check if GET parameter exists +if(request->hasParam("download")) + AsyncWebParameter* p = request->getParam("download"); + +//Check if POST (but not File) parameter exists +if(request->hasParam("download", true)) + AsyncWebParameter* p = request->getParam("download", true); + +//Check if FILE was uploaded +if(request->hasParam("download", true, true)) + AsyncWebParameter* p = request->getParam("download", true, true); + +//List all parameters (Compatibility) +int args = request->args(); +for(int i=0;iargName(i).c_str(), request->arg(i).c_str()); +} + +//Check if parameter exists (Compatibility) +if(request->hasArg("download")) + String arg = request->arg("download"); +``` + +### FILE Upload handling +```cpp +void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + if(!index){ + Serial.printf("UploadStart: %s\n", filename.c_str()); + } + for(size_t i=0; i(); + // ... +}); +server.addHandler(handler); +``` + +## Responses +### Redirect to another URL +```cpp +//to local url +request->redirect("/login"); + +//to external url +request->redirect("http://esp8266.com"); +``` + +### Basic response with HTTP Code +```cpp +request->send(404); //Sends 404 File Not Found +``` + +### Basic response with HTTP Code and extra headers +```cpp +AsyncWebServerResponse *response = request->beginResponse(404); //Sends 404 File Not Found +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Basic response with string content +```cpp +request->send(200, "text/plain", "Hello World!"); +``` + +### Basic response with string content and extra headers +```cpp +AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!"); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Send large webpage from PROGMEM +```cpp +const char index_html[] PROGMEM = "..."; // large char array, tested with 14k +request->send_P(200, "text/html", index_html); +``` + +### Send large webpage from PROGMEM and extra headers +```cpp +const char index_html[] PROGMEM = "..."; // large char array, tested with 14k +AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Send large webpage from PROGMEM containing templates +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +const char index_html[] PROGMEM = "..."; // large char array, tested with 14k +request->send_P(200, "text/html", index_html, processor); +``` + +### Send large webpage from PROGMEM containing templates and extra headers +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +const char index_html[] PROGMEM = "..."; // large char array, tested with 14k +AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html, processor); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Send binary content from PROGMEM +```cpp + +//File: favicon.ico.gz, Size: 726 +#define favicon_ico_gz_len 726 +const uint8_t favicon_ico_gz[] PROGMEM = { + 0x1F, 0x8B, 0x08, 0x08, 0x0B, 0x87, 0x90, 0x57, 0x00, 0x03, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6F, + 0x6E, 0x2E, 0x69, 0x63, 0x6F, 0x00, 0xCD, 0x53, 0x5F, 0x48, 0x9A, 0x51, 0x14, 0xBF, 0x62, 0x6D, + 0x86, 0x96, 0xA9, 0x64, 0xD3, 0xFE, 0xA8, 0x99, 0x65, 0x1A, 0xB4, 0x8A, 0xA8, 0x51, 0x54, 0x23, + 0xA8, 0x11, 0x49, 0x51, 0x8A, 0x34, 0x62, 0x93, 0x85, 0x31, 0x58, 0x44, 0x12, 0x45, 0x2D, 0x58, + 0xF5, 0x52, 0x41, 0x10, 0x23, 0x82, 0xA0, 0x20, 0x98, 0x2F, 0xC1, 0x26, 0xED, 0xA1, 0x20, 0x89, + 0x04, 0xD7, 0x83, 0x58, 0x20, 0x28, 0x04, 0xAB, 0xD1, 0x9B, 0x8C, 0xE5, 0xC3, 0x60, 0x32, 0x64, + 0x0E, 0x56, 0xBF, 0x9D, 0xEF, 0xF6, 0x30, 0x82, 0xED, 0xAD, 0x87, 0xDD, 0x8F, 0xF3, 0xDD, 0x8F, + 0x73, 0xCF, 0xEF, 0x9C, 0xDF, 0x39, 0xBF, 0xFB, 0x31, 0x26, 0xA2, 0x27, 0x37, 0x97, 0xD1, 0x5B, + 0xCF, 0x9E, 0x67, 0x30, 0xA6, 0x66, 0x8C, 0x99, 0xC9, 0xC8, 0x45, 0x9E, 0x6B, 0x3F, 0x5F, 0x74, + 0xA6, 0x94, 0x5E, 0xDB, 0xFF, 0xB2, 0xE6, 0xE7, 0xE7, 0xF9, 0xDE, 0xD6, 0xD6, 0x96, 0xDB, 0xD8, + 0xD8, 0x78, 0xBF, 0xA1, 0xA1, 0xC1, 0xDA, 0xDC, 0xDC, 0x2C, 0xEB, 0xED, 0xED, 0x15, 0x9B, 0xCD, + 0xE6, 0x4A, 0x83, 0xC1, 0xE0, 0x2E, 0x29, 0x29, 0x99, 0xD6, 0x6A, 0xB5, 0x4F, 0x75, 0x3A, 0x9D, + 0x61, 0x75, 0x75, 0x95, 0xB5, 0xB7, 0xB7, 0xDF, 0xC8, 0xD1, 0xD4, 0xD4, 0xF4, 0xB0, 0xBA, 0xBA, + 0xFA, 0x83, 0xD5, 0x6A, 0xFD, 0x5A, 0x5E, 0x5E, 0x9E, 0x28, 0x2D, 0x2D, 0x0D, 0x10, 0xC6, 0x4B, + 0x98, 0x78, 0x5E, 0x5E, 0xDE, 0x95, 0x42, 0xA1, 0x40, 0x4E, 0x4E, 0xCE, 0x65, 0x76, 0x76, 0xF6, + 0x47, 0xB5, 0x5A, 0x6D, 0x4F, 0x26, 0x93, 0xA2, 0xD6, 0xD6, 0x56, 0x8E, 0x6D, 0x69, 0x69, 0xD1, + 0x11, 0x36, 0x62, 0xB1, 0x58, 0x60, 0x32, 0x99, 0xA0, 0xD7, 0xEB, 0x51, 0x58, 0x58, 0x88, 0xFC, + 0xFC, 0x7C, 0x10, 0x16, 0x02, 0x56, 0x2E, 0x97, 0x43, 0x2A, 0x95, 0x42, 0x2C, 0x16, 0x23, 0x33, + 0x33, 0x33, 0xAE, 0x52, 0xA9, 0x1E, 0x64, 0x65, 0x65, 0x71, 0x7C, 0x7D, 0x7D, 0xBD, 0x93, 0xEA, + 0xFE, 0x30, 0x1A, 0x8D, 0xE8, 0xEC, 0xEC, 0xC4, 0xE2, 0xE2, 0x22, 0x6A, 0x6A, 0x6A, 0x40, 0x39, + 0x41, 0xB5, 0x38, 0x4E, 0xC8, 0x33, 0x3C, 0x3C, 0x0C, 0x87, 0xC3, 0xC1, 0x6B, 0x54, 0x54, 0x54, + 0xBC, 0xE9, 0xEB, 0xEB, 0x93, 0x5F, 0x5C, 0x5C, 0x30, 0x8A, 0x9D, 0x2E, 0x2B, 0x2B, 0xBB, 0xA2, + 0x3E, 0x41, 0xBD, 0x21, 0x1E, 0x8F, 0x63, 0x6A, 0x6A, 0x0A, 0x81, 0x40, 0x00, 0x94, 0x1B, 0x3D, + 0x3D, 0x3D, 0x42, 0x3C, 0x96, 0x96, 0x96, 0x70, 0x7E, 0x7E, 0x8E, 0xE3, 0xE3, 0x63, 0xF8, 0xFD, + 0xFE, 0xB4, 0xD7, 0xEB, 0xF5, 0x8F, 0x8F, 0x8F, 0x5B, 0x68, 0x5E, 0x6F, 0x05, 0xCE, 0xB4, 0xE3, + 0xE8, 0xE8, 0x08, 0x27, 0x27, 0x27, 0xD8, 0xDF, 0xDF, 0xC7, 0xD9, 0xD9, 0x19, 0x6C, 0x36, 0x1B, + 0x36, 0x36, 0x36, 0x38, 0x9F, 0x85, 0x85, 0x05, 0xAC, 0xAF, 0xAF, 0x23, 0x1A, 0x8D, 0x22, 0x91, + 0x48, 0x20, 0x16, 0x8B, 0xFD, 0xDA, 0xDA, 0xDA, 0x7A, 0x41, 0x33, 0x7E, 0x57, 0x50, 0x50, 0x80, + 0x89, 0x89, 0x09, 0x84, 0xC3, 0x61, 0x6C, 0x6F, 0x6F, 0x23, 0x12, 0x89, 0xE0, 0xE0, 0xE0, 0x00, + 0x43, 0x43, 0x43, 0x58, 0x5E, 0x5E, 0xE6, 0x9C, 0x7D, 0x3E, 0x1F, 0x46, 0x47, 0x47, 0x79, 0xBE, + 0xBD, 0xBD, 0x3D, 0xE1, 0x3C, 0x1D, 0x0C, 0x06, 0x9F, 0x10, 0xB7, 0xC7, 0x84, 0x4F, 0xF6, 0xF7, + 0xF7, 0x63, 0x60, 0x60, 0x00, 0x83, 0x83, 0x83, 0x18, 0x19, 0x19, 0xC1, 0xDC, 0xDC, 0x1C, 0x8F, + 0x17, 0x7C, 0xA4, 0x27, 0xE7, 0x34, 0x39, 0x39, 0x89, 0x9D, 0x9D, 0x1D, 0x6E, 0x54, 0xE3, 0x13, + 0xE5, 0x34, 0x11, 0x37, 0x49, 0x51, 0x51, 0xD1, 0x4B, 0xA5, 0x52, 0xF9, 0x45, 0x26, 0x93, 0x5D, + 0x0A, 0xF3, 0x92, 0x48, 0x24, 0xA0, 0x6F, 0x14, 0x17, 0x17, 0xA3, 0xB6, 0xB6, 0x16, 0x5D, 0x5D, + 0x5D, 0x7C, 0x1E, 0xBB, 0xBB, 0xBB, 0x9C, 0xD7, 0xE1, 0xE1, 0x21, 0x42, 0xA1, 0xD0, 0x6B, 0xD2, + 0x45, 0x4C, 0x33, 0x12, 0x34, 0xCC, 0xA0, 0x19, 0x54, 0x92, 0x56, 0x0E, 0xD2, 0xD9, 0x43, 0xF8, + 0xCF, 0x82, 0x56, 0xC2, 0xDC, 0xEB, 0xEA, 0xEA, 0x38, 0x7E, 0x6C, 0x6C, 0x4C, 0xE0, 0xFE, 0x9D, + 0xB8, 0xBF, 0xA7, 0xFA, 0xAF, 0x56, 0x56, 0x56, 0xEE, 0x6D, 0x6E, 0x6E, 0xDE, 0xB8, 0x47, 0x55, + 0x55, 0x55, 0x6C, 0x66, 0x66, 0x46, 0x44, 0xDA, 0x3B, 0x34, 0x1A, 0x4D, 0x94, 0xB0, 0x3F, 0x09, + 0x7B, 0x45, 0xBD, 0xA5, 0x5D, 0x2E, 0x57, 0x8C, 0x7A, 0x73, 0xD9, 0xED, 0xF6, 0x3B, 0x84, 0xFF, + 0xE7, 0x7D, 0xA6, 0x3A, 0x2C, 0x95, 0x4A, 0xB1, 0x8E, 0x8E, 0x0E, 0x6D, 0x77, 0x77, 0xB7, 0xCD, + 0xE9, 0x74, 0x3E, 0x73, 0xBB, 0xDD, 0x8F, 0x3C, 0x1E, 0x8F, 0xE6, 0xF4, 0xF4, 0x94, 0xAD, 0xAD, + 0xAD, 0xDD, 0xDE, 0xCF, 0x73, 0x0B, 0x0B, 0xB8, 0xB6, 0xE0, 0x5D, 0xC6, 0x66, 0xC5, 0xE4, 0x10, + 0x4C, 0xF4, 0xF7, 0xD8, 0x59, 0xF2, 0x7F, 0xA3, 0xB8, 0xB4, 0xFC, 0x0F, 0xEE, 0x37, 0x70, 0xEC, + 0x16, 0x4A, 0x7E, 0x04, 0x00, 0x00 +}; + +AsyncWebServerResponse *response = request->beginResponse_P(200, "image/x-icon", favicon_ico_gz, favicon_ico_gz_len); +response->addHeader("Content-Encoding", "gzip"); +request->send(response); +``` + +### Respond with content coming from a Stream +```cpp +//read 12 bytes from Serial and send them as Content Type text/plain +request->send(Serial, "text/plain", 12); +``` + +### Respond with content coming from a Stream and extra headers +```cpp +//read 12 bytes from Serial and send them as Content Type text/plain +AsyncWebServerResponse *response = request->beginResponse(Serial, "text/plain", 12); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Respond with content coming from a Stream containing templates +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +//read 12 bytes from Serial and send them as Content Type text/plain +request->send(Serial, "text/plain", 12, processor); +``` + +### Respond with content coming from a Stream containing templates and extra headers +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +//read 12 bytes from Serial and send them as Content Type text/plain +AsyncWebServerResponse *response = request->beginResponse(Serial, "text/plain", 12, processor); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Respond with content coming from a File +```cpp +//Send index.htm with default content type +request->send(SPIFFS, "/index.htm"); + +//Send index.htm as text +request->send(SPIFFS, "/index.htm", "text/plain"); + +//Download index.htm +request->send(SPIFFS, "/index.htm", String(), true); +``` + +### Respond with content coming from a File and extra headers +```cpp +//Send index.htm with default content type +AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm"); + +//Send index.htm as text +AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", "text/plain"); + +//Download index.htm +AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", String(), true); + +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Respond with content coming from a File containing templates +Internally uses [Chunked Response](#chunked-response). + +Index.htm contents: +``` +%HELLO_FROM_TEMPLATE% +``` + +Somewhere in source files: +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +//Send index.htm with template processor function +request->send(SPIFFS, "/index.htm", String(), false, processor); +``` + +### Respond with content using a callback +```cpp +//send 128 bytes as plain text +request->send("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + //Write up to "maxLen" bytes into "buffer" and return the amount written. + //index equals the amount of bytes that have been already sent + //You will not be asked for more bytes once the content length has been reached. + //Keep in mind that you can not delay or yield waiting for more data! + //Send what you currently have and you will be asked for more again + return mySource.read(buffer, maxLen); +}); +``` + +### Respond with content using a callback and extra headers +```cpp +//send 128 bytes as plain text +AsyncWebServerResponse *response = request->beginResponse("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + //Write up to "maxLen" bytes into "buffer" and return the amount written. + //index equals the amount of bytes that have been already sent + //You will not be asked for more bytes once the content length has been reached. + //Keep in mind that you can not delay or yield waiting for more data! + //Send what you currently have and you will be asked for more again + return mySource.read(buffer, maxLen); +}); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Respond with content using a callback containing templates +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +//send 128 bytes as plain text +request->send("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + //Write up to "maxLen" bytes into "buffer" and return the amount written. + //index equals the amount of bytes that have been already sent + //You will not be asked for more bytes once the content length has been reached. + //Keep in mind that you can not delay or yield waiting for more data! + //Send what you currently have and you will be asked for more again + return mySource.read(buffer, maxLen); +}, processor); +``` + +### Respond with content using a callback containing templates and extra headers +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +//send 128 bytes as plain text +AsyncWebServerResponse *response = request->beginResponse("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + //Write up to "maxLen" bytes into "buffer" and return the amount written. + //index equals the amount of bytes that have been already sent + //You will not be asked for more bytes once the content length has been reached. + //Keep in mind that you can not delay or yield waiting for more data! + //Send what you currently have and you will be asked for more again + return mySource.read(buffer, maxLen); +}, processor); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Chunked Response +Used when content length is unknown. Works best if the client supports HTTP/1.1 +```cpp +AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + //Write up to "maxLen" bytes into "buffer" and return the amount written. + //index equals the amount of bytes that have been already sent + //You will be asked for more data until 0 is returned + //Keep in mind that you can not delay or yield waiting for more data! + return mySource.read(buffer, maxLen); +}); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Chunked Response containing templates +Used when content length is unknown. Works best if the client supports HTTP/1.1 +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + //Write up to "maxLen" bytes into "buffer" and return the amount written. + //index equals the amount of bytes that have been already sent + //You will be asked for more data until 0 is returned + //Keep in mind that you can not delay or yield waiting for more data! + return mySource.read(buffer, maxLen); +}, processor); +response->addHeader("Server","ESP Async Web Server"); +request->send(response); +``` + +### Print to response +```cpp +AsyncResponseStream *response = request->beginResponseStream("text/html"); +response->addHeader("Server","ESP Async Web Server"); +response->printf("Webpage at %s", request->url().c_str()); + +response->print("

Hello "); +response->print(request->client()->remoteIP()); +response->print("

"); + +response->print("

General

"); +response->print("
    "); +response->printf("
  • Version: HTTP/1.%u
  • ", request->version()); +response->printf("
  • Method: %s
  • ", request->methodToString()); +response->printf("
  • URL: %s
  • ", request->url().c_str()); +response->printf("
  • Host: %s
  • ", request->host().c_str()); +response->printf("
  • ContentType: %s
  • ", request->contentType().c_str()); +response->printf("
  • ContentLength: %u
  • ", request->contentLength()); +response->printf("
  • Multipart: %s
  • ", request->multipart()?"true":"false"); +response->print("
"); + +response->print("

Headers

"); +response->print("
    "); +int headers = request->headers(); +for(int i=0;igetHeader(i); + response->printf("
  • %s: %s
  • ", h->name().c_str(), h->value().c_str()); +} +response->print("
"); + +response->print("

Parameters

"); +response->print("
    "); +int params = request->params(); +for(int i=0;igetParam(i); + if(p->isFile()){ + response->printf("
  • FILE[%s]: %s, size: %u
  • ", p->name().c_str(), p->value().c_str(), p->size()); + } else if(p->isPost()){ + response->printf("
  • POST[%s]: %s
  • ", p->name().c_str(), p->value().c_str()); + } else { + response->printf("
  • GET[%s]: %s
  • ", p->name().c_str(), p->value().c_str()); + } +} +response->print("
"); + +response->print(""); +//send the response last +request->send(response); +``` + +### ArduinoJson Basic Response +This way of sending Json is great for when the result is below 4KB +```cpp +#include "AsyncJson.h" +#include "ArduinoJson.h" + + +AsyncResponseStream *response = request->beginResponseStream("application/json"); +DynamicJsonBuffer jsonBuffer; +JsonObject &root = jsonBuffer.createObject(); +root["heap"] = ESP.getFreeHeap(); +root["ssid"] = WiFi.SSID(); +root.printTo(*response); +request->send(response); +``` + +### ArduinoJson Advanced Response +This response can handle really large Json objects (tested to 40KB) +There isn't any noticeable speed decrease for small results with the method above +Since ArduinoJson does not allow reading parts of the string, the whole Json has to +be passed every time a chunks needs to be sent, which shows speed decrease proportional +to the resulting json packets +```cpp +#include "AsyncJson.h" +#include "ArduinoJson.h" + + +AsyncJsonResponse * response = new AsyncJsonResponse(); +response->addHeader("Server","ESP Async Web Server"); +JsonObject& root = response->getRoot(); +root["heap"] = ESP.getFreeHeap(); +root["ssid"] = WiFi.SSID(); +response->setLength(); +request->send(response); +``` + +## Serving static files +In addition to serving files from SPIFFS as described above, the server provide a dedicated handler that optimize the +performance of serving files from SPIFFS - ```AsyncStaticWebHandler```. Use ```server.serveStatic()``` function to +initialize and add a new instance of ```AsyncStaticWebHandler``` to the server. +The Handler will not handle the request if the file does not exists, e.g. the server will continue to look for another +handler that can handle the request. +Notice that you can chain setter functions to setup the handler, or keep a pointer to change it at a later time. + +### Serving specific file by name +```cpp +// Serve the file "/www/page.htm" when request url is "/page.htm" +server.serveStatic("/page.htm", SPIFFS, "/www/page.htm"); +``` + +### Serving files in directory +To serve files in a directory, the path to the files should specify a directory in SPIFFS and ends with "/". +```cpp +// Serve files in directory "/www/" when request url starts with "/" +// Request to the root or none existing files will try to server the defualt +// file name "index.htm" if exists +server.serveStatic("/", SPIFFS, "/www/"); + +// Server with different default file +server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("default.html"); +``` + +### Serving static files with authentication + +```cpp +server + .serveStatic("/", SPIFFS, "/www/") + .setDefaultFile("default.html") + .setAuthentication("user", "pass"); +``` + +### Specifying Cache-Control header +It is possible to specify Cache-Control header value to reduce the number of calls to the server once the client loaded +the files. For more information on Cache-Control values see [Cache-Control](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) +```cpp +// Cache responses for 10 minutes (600 seconds) +server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600"); + +//*** Change Cache-Control after server setup *** + +// During setup - keep a pointer to the handler +AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600"); + +// At a later event - change Cache-Control +handler->setCacheControl("max-age=30"); +``` + +### Specifying Date-Modified header +It is possible to specify Date-Modified header to enable the server to return Not-Modified (304) response for requests +with "If-Modified-Since" header with the same value, instead of responding with the actual file content. +```cpp +// Update the date modified string every time files are updated +server.serveStatic("/", SPIFFS, "/www/").setLastModified("Mon, 20 Jun 2016 14:00:00 GMT"); + +//*** Chage last modified value at a later stage *** + +// During setup - read last modified value from config or EEPROM +String date_modified = loadDateModified(); +AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/"); +handler->setLastModified(date_modified); + +// At a later event when files are updated +String date_modified = getNewDateModfied(); +saveDateModified(date_modified); // Save for next reset +handler->setLastModified(date_modified); +``` + +### Specifying Template Processor callback +It is possible to specify template processor for static files. For information on template processor see +[Respond with content coming from a File containing templates](#respond-with-content-coming-from-a-file-containing-templates). +```cpp +String processor(const String& var) +{ + if(var == "HELLO_FROM_TEMPLATE") + return F("Hello world!"); + return String(); +} + +// ... + +server.serveStatic("/", SPIFFS, "/www/").setTemplateProcessor(processor); +``` + +## Param Rewrite With Matching +It is possible to rewrite the request url with parameter matchg. Here is an example with one parameter: +Rewrite for example "/radio/{frequence}" -> "/radio?f={frequence}" + +```cpp +class OneParamRewrite : public AsyncWebRewrite +{ + protected: + String _urlPrefix; + int _paramIndex; + String _paramsBackup; + + public: + OneParamRewrite(const char* from, const char* to) + : AsyncWebRewrite(from, to) { + + _paramIndex = _from.indexOf('{'); + + if( _paramIndex >=0 && _from.endsWith("}")) { + _urlPrefix = _from.substring(0, _paramIndex); + int index = _params.indexOf('{'); + if(index >= 0) { + _params = _params.substring(0, index); + } + } else { + _urlPrefix = _from; + } + _paramsBackup = _params; + } + + bool match(AsyncWebServerRequest *request) override { + if(request->url().startsWith(_urlPrefix)) { + if(_paramIndex >= 0) { + _params = _paramsBackup + request->url().substring(_paramIndex); + } else { + _params = _paramsBackup; + } + return true; + + } else { + return false; + } + } +}; +``` + +Usage: + +```cpp + server.addRewrite( new OneParamRewrite("/radio/{frequence}", "/radio?f={frequence}") ); +``` + +## Using filters +Filters can be set to `Rewrite` or `Handler` in order to control when to apply the rewrite and consider the handler. +A filter is a callback function that evaluates the request and return a boolean `true` to include the item +or `false` to exclude it. +Two filter callback are provided for convince: +* `ON_STA_FILTER` - return true when requests are made to the STA (station mode) interface. +* `ON_AP_FILTER` - return true when requests are made to the AP (access point) interface. + +### Serve different site files in AP mode +```cpp +server.serveStatic("/", SPIFFS, "/www/").setFilter(ON_STA_FILTER); +server.serveStatic("/", SPIFFS, "/ap/").setFilter(ON_AP_FILTER); +``` + +### Rewrite to different index on AP +```cpp +// Serve the file "/www/index-ap.htm" in AP, and the file "/www/index.htm" on STA +server.rewrite("/", "index.htm"); +server.rewrite("/index.htm", "index-ap.htm").setFilter(ON_AP_FILTER); +server.serveStatic("/", SPIFFS, "/www/"); +``` + +### Serving different hosts +```cpp +// Filter callback using request host +bool filterOnHost1(AsyncWebServerRequest *request) { return request->host() == "host1"; } + +// Server setup: server files in "/host1/" to requests for "host1", and files in "/www/" otherwise. +server.serveStatic("/", SPIFFS, "/host1/").setFilter(filterOnHost1); +server.serveStatic("/", SPIFFS, "/www/"); +``` + +### Determine interface inside callbacks +```cpp + String RedirectUrl = "http://"; + if (ON_STA_FILTER(request)) { + RedirectUrl += WiFi.localIP().toString(); + } else { + RedirectUrl += WiFi.softAPIP().toString(); + } + RedirectUrl += "/index.htm"; + request->redirect(RedirectUrl); +``` + +## Bad Responses +Some responses are implemented, but you should not use them, because they do not conform to HTTP. +The following example will lead to unclean close of the connection and more time wasted +than providing the length of the content + +### Respond with content using a callback without content length to HTTP/1.0 clients +```cpp +//This is used as fallback for chunked responses to HTTP/1.0 Clients +request->send("text/plain", 0, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + //Write up to "maxLen" bytes into "buffer" and return the amount written. + //You will be asked for more data until 0 is returned + //Keep in mind that you can not delay or yield waiting for more data! + return mySource.read(buffer, maxLen); +}); +``` + +## Async WebSocket Plugin +The server includes a web socket plugin which lets you define different WebSocket locations to connect to +without starting another listening service or using different port + +### Async WebSocket Event +```cpp + +void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(type == WS_EVT_CONNECT){ + //client connected + os_printf("ws[%s][%u] connect\n", server->url(), client->id()); + client->printf("Hello Client %u :)", client->id()); + client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + //client disconnected + os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); + } else if(type == WS_EVT_ERROR){ + //error was received from the other end + os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); + } else if(type == WS_EVT_PONG){ + //pong message was received (in response to a ping request maybe) + os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); + } else if(type == WS_EVT_DATA){ + //data packet + AwsFrameInfo * info = (AwsFrameInfo*)arg; + if(info->final && info->index == 0 && info->len == len){ + //the whole message is in a single frame and we got all of it's data + os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); + if(info->opcode == WS_TEXT){ + data[len] = 0; + os_printf("%s\n", (char*)data); + } else { + for(size_t i=0; i < info->len; i++){ + os_printf("%02x ", data[i]); + } + os_printf("\n"); + } + if(info->opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } else { + //message is comprised of multiple frames or the frame is split into multiple packets + if(info->index == 0){ + if(info->num == 0) + os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); + } + + os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); + if(info->message_opcode == WS_TEXT){ + data[len] = 0; + os_printf("%s\n", (char*)data); + } else { + for(size_t i=0; i < len; i++){ + os_printf("%02x ", data[i]); + } + os_printf("\n"); + } + + if((info->index + len) == info->len){ + os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); + if(info->final){ + os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + if(info->message_opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } + } + } + } +} +``` + +### Methods for sending data to a socket client +```cpp + + + +//Server methods +AsyncWebSocket ws("/ws"); +//printf to a client +ws.printf((uint32_t)client_id, arguments...); +//printf to all clients +ws.printfAll(arguments...); +//printf_P to a client +ws.printf_P((uint32_t)client_id, PSTR(format), arguments...); +//printfAll_P to all clients +ws.printfAll_P(PSTR(format), arguments...); +//send text to a client +ws.text((uint32_t)client_id, (char*)text); +ws.text((uint32_t)client_id, (uint8_t*)text, (size_t)len); +//send text from PROGMEM to a client +ws.text((uint32_t)client_id, PSTR("text")); +const char flash_text[] PROGMEM = "Text to send" +ws.text((uint32_t)client_id, FPSTR(flash_text)); +//send text to all clients +ws.textAll((char*)text); +ws.textAll((uint8_t*)text, (size_t)len); +//send binary to a client +ws.binary((uint32_t)client_id, (char*)binary); +ws.binary((uint32_t)client_id, (uint8_t*)binary, (size_t)len); +//send binary from PROGMEM to a client +const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 }; +ws.binary((uint32_t)client_id, flash_binary, 4); +//send binary to all clients +ws.binaryAll((char*)binary); +ws.binaryAll((uint8_t*)binary, (size_t)len); +//HTTP Authenticate before switch to Websocket protocol +ws.setAuthentication("user", "pass"); + +//client methods +AsyncWebSocketClient * client; +//printf +client->printf(arguments...); +//printf_P +client->printf_P(PSTR(format), arguments...); +//send text +client->text((char*)text); +client->text((uint8_t*)text, (size_t)len); +//send text from PROGMEM +client->text(PSTR("text")); +const char flash_text[] PROGMEM = "Text to send"; +client->text(FPSTR(flash_text)); +//send binary +client->binary((char*)binary); +client->binary((uint8_t*)binary, (size_t)len); +//send binary from PROGMEM +const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 }; +client->binary(flash_binary, 4); +``` + +### Direct access to web socket message buffer +When sending a web socket message using the above methods a buffer is created. Under certain circumstances you might want to manipulate or populate this buffer directly from your application, for example to prevent unnecessary duplications of the data. This example below shows how to create a buffer and print data to it from an ArduinoJson object then send it. + +```cpp +void sendDataWs(AsyncWebSocketClient * client) +{ + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + root["a"] = "abc"; + root["b"] = "abcd"; + root["c"] = "abcde"; + root["d"] = "abcdef"; + root["e"] = "abcdefg"; + size_t len = root.measureLength(); + AsyncWebSocketMessageBuffer * buffer = ws.makeBuffer(len); // creates a buffer (len + 1) for you. + if (buffer) { + root.printTo((char *)buffer->get(), len + 1); + if (client) { + client->text(buffer); + } else { + ws.textAll(buffer); + } + } +} +``` + +### Limiting the number of web socket clients +Browsers sometimes do not correctly close the websocket connection, even when the close() function is called in javascript. This will eventually exhaust the web server's resources and will cause the server to crash. Periodically calling the cleanClients() function from the main loop() function limits the number of clients by closing the oldest client when the maximum number of clients has been exceeded. This can called be every cycle, however, if you wish to use less power, then calling as infrequently as once per second is sufficient. + +```cpp +void loop(){ + ws.cleanupClients(); +} +``` + + +## Async Event Source Plugin +The server includes EventSource (Server-Sent Events) plugin which can be used to send short text events to the browser. +Difference between EventSource and WebSockets is that EventSource is single direction, text-only protocol. + +### Setup Event Source on the server +```cpp +AsyncWebServer server(80); +AsyncEventSource events("/events"); + +void setup(){ + // setup ...... + events.onConnect([](AsyncEventSourceClient *client){ + if(client->lastId()){ + Serial.printf("Client reconnected! Last message ID that it gat is: %u\n", client->lastId()); + } + //send event with message "hello!", id current millis + // and set reconnect delay to 1 second + client->send("hello!",NULL,millis(),1000); + }); + //HTTP Basic authentication + events.setAuthentication("user", "pass"); + server.addHandler(&events); + // setup ...... +} + +void loop(){ + if(eventTriggered){ // your logic here + //send event "myevent" + events.send("my event content","myevent",millis()); + } +} +``` + +### Setup Event Source in the browser +```javascript +if (!!window.EventSource) { + var source = new EventSource('/events'); + + source.addEventListener('open', function(e) { + console.log("Events Connected"); + }, false); + + source.addEventListener('error', function(e) { + if (e.target.readyState != EventSource.OPEN) { + console.log("Events Disconnected"); + } + }, false); + + source.addEventListener('message', function(e) { + console.log("message", e.data); + }, false); + + source.addEventListener('myevent', function(e) { + console.log("myevent", e.data); + }, false); +} +``` + +## Scanning for available WiFi Networks +```cpp +//First request will return 0 results unless you start scan from somewhere else (loop/setup) +//Do not request more often than 3-5 seconds +server.on("/scan", HTTP_GET, [](AsyncWebServerRequest *request){ + String json = "["; + int n = WiFi.scanComplete(); + if(n == -2){ + WiFi.scanNetworks(true); + } else if(n){ + for (int i = 0; i < n; ++i){ + if(i) json += ","; + json += "{"; + json += "\"rssi\":"+String(WiFi.RSSI(i)); + json += ",\"ssid\":\""+WiFi.SSID(i)+"\""; + json += ",\"bssid\":\""+WiFi.BSSIDstr(i)+"\""; + json += ",\"channel\":"+String(WiFi.channel(i)); + json += ",\"secure\":"+String(WiFi.encryptionType(i)); + json += ",\"hidden\":"+String(WiFi.isHidden(i)?"true":"false"); + json += "}"; + } + WiFi.scanDelete(); + if(WiFi.scanComplete() == -2){ + WiFi.scanNetworks(true); + } + } + json += "]"; + request->send(200, "application/json", json); + json = String(); +}); +``` + +## Remove handlers and rewrites + +Server goes through handlers in same order as they were added. You can't simple add handler with same path to override them. +To remove handler: +```arduino +// save callback for particular URL path +auto handler = server.on("/some/path", [](AsyncWebServerRequest *request){ + //do something useful +}); +// when you don't need handler anymore remove it +server.removeHandler(&handler); + +// same with rewrites +server.removeRewrite(&someRewrite); + +server.onNotFound([](AsyncWebServerRequest *request){ + request->send(404); +}); + +// remove server.onNotFound handler +server.onNotFound(NULL); + +// remove all rewrites, handlers and onNotFound/onFileUpload/onRequestBody callbacks +server.reset(); +``` + +## Setting up the server +```cpp +#include "ESPAsyncTCP.h" +#include "ESPAsyncWebServer.h" + +AsyncWebServer server(80); +AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws +AsyncEventSource events("/events"); // event source (Server-Sent events) + +const char* ssid = "your-ssid"; +const char* password = "your-pass"; +const char* http_username = "admin"; +const char* http_password = "admin"; + +//flag to use from web update to reboot the ESP +bool shouldReboot = false; + +void onRequest(AsyncWebServerRequest *request){ + //Handle Unknown Request + request->send(404); +} + +void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ + //Handle body +} + +void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + //Handle upload +} + +void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + //Handle WebSocket event +} + +void setup(){ + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + + // attach AsyncWebSocket + ws.onEvent(onEvent); + server.addHandler(&ws); + + // attach AsyncEventSource + server.addHandler(&events); + + // respond to GET requests on URL /heap + server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", String(ESP.getFreeHeap())); + }); + + // upload a file to /upload + server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ + request->send(200); + }, onUpload); + + // send a file when /index is requested + server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){ + request->send(SPIFFS, "/index.htm"); + }); + + // HTTP basic authentication + server.on("/login", HTTP_GET, [](AsyncWebServerRequest *request){ + if(!request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + request->send(200, "text/plain", "Login Success!"); + }); + + // Simple Firmware Update Form + server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/html", "
"); + }); + server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ + shouldReboot = !Update.hasError(); + AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot?"OK":"FAIL"); + response->addHeader("Connection", "close"); + request->send(response); + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + if(!index){ + Serial.printf("Update Start: %s\n", filename.c_str()); + Update.runAsync(true); + if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)){ + Update.printError(Serial); + } + } + if(!Update.hasError()){ + if(Update.write(data, len) != len){ + Update.printError(Serial); + } + } + if(final){ + if(Update.end(true)){ + Serial.printf("Update Success: %uB\n", index+len); + } else { + Update.printError(Serial); + } + } + }); + + // attach filesystem root at URL /fs + server.serveStatic("/fs", SPIFFS, "/"); + + // Catch-All Handlers + // Any request that can not find a Handler that canHandle it + // ends in the callbacks below. + server.onNotFound(onRequest); + server.onFileUpload(onUpload); + server.onRequestBody(onBody); + + server.begin(); +} + +void loop(){ + if(shouldReboot){ + Serial.println("Rebooting..."); + delay(100); + ESP.restart(); + } + static char temp[128]; + sprintf(temp, "Seconds since boot: %u", millis()/1000); + events.send(temp, "time"); //send event "time" +} +``` + +### Setup global and class functions as request handlers + +```cpp +#include +#include +#include +#include + +void handleRequest(AsyncWebServerRequest *request){} + +class WebClass { +public : + AsyncWebServer classWebServer = AsyncWebServer(81); + + WebClass(){}; + + void classRequest (AsyncWebServerRequest *request){} + + void begin(){ + // attach global request handler + classWebServer.on("/example", HTTP_ANY, handleRequest); + + // attach class request handler + classWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, this, std::placeholders::_1)); + } +}; + +AsyncWebServer globalWebServer(80); +WebClass webClassInstance; + +void setup() { + // attach global request handler + globalWebServer.on("/example", HTTP_ANY, handleRequest); + + // attach class request handler + globalWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1)); +} + +void loop() { + +} +``` + +### Methods for controlling websocket connections + +```cpp + // Disable client connections if it was activated + if ( ws.enabled() ) + ws.enable(false); + + // enable client connections if it was disabled + if ( !ws.enabled() ) + ws.enable(true); +``` + +Example of OTA code + +```cpp + // OTA callbacks + ArduinoOTA.onStart([]() { + // Clean SPIFFS + SPIFFS.end(); + + // Disable client connections + ws.enable(false); + + // Advertise connected clients what's going on + ws.textAll("OTA Update Started"); + + // Close them + ws.closeAll(); + + }); + +``` + +### Adding Default Headers + +In some cases, such as when working with CORS, or with some sort of custom authentication system, +you might need to define a header that should get added to all responses (including static, websocket and EventSource). +The DefaultHeaders singleton allows you to do this. + +Example: + +```cpp +DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); +webServer.begin(); +``` + +*NOTE*: You will still need to respond to the OPTIONS method for CORS pre-flight in most cases. (unless you are only using GET) + +This is one option: + +```cpp +webServer.onNotFound([](AsyncWebServerRequest *request) { + if (request->method() == HTTP_OPTIONS) { + request->send(200); + } else { + request->send(404); + } +}); +``` + +### Path variable + +With path variable you can create a custom regex rule for a specific parameter in a route. +For example we want a `sensorId` parameter in a route rule to match only a integer. + +```cpp + server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { + String sensorId = request->pathArg(0); + }); +``` +*NOTE*: All regex patterns starts with `^` and ends with `$` + +To enable the `Path variable` support, you have to define the buildflag `-DASYNCWEBSERVER_REGEX`. + + +For Arduino IDE create/update `platform.local.txt`: + +`Windows`: C:\Users\(username)\AppData\Local\Arduino15\packages\\`{espxxxx}`\hardware\\`espxxxx`\\`{version}`\platform.local.txt + +`Linux`: ~/.arduino15/packages/`{espxxxx}`/hardware/`{espxxxx}`/`{version}`/platform.local.txt + +Add/Update the following line: +``` + compiler.cpp.extra_flags=-DDASYNCWEBSERVER_REGEX +``` + +For platformio modify `platformio.ini`: +```ini +[env:myboard] +build_flags = + -DASYNCWEBSERVER_REGEX +``` +*NOTE*: By enabling `ASYNCWEBSERVER_REGEX`, `` will be included. This will add an 100k to your binary. diff --git a/pixelart-controller/lib/ESPAsyncWebServer/_config.yml b/pixelart-controller/lib/ESPAsyncWebServer/_config.yml new file mode 100644 index 0000000..c419263 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/pixelart-controller/lib/ESPAsyncWebServer/component.mk b/pixelart-controller/lib/ESPAsyncWebServer/component.mk new file mode 100644 index 0000000..bb5bb16 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := src +COMPONENT_SRCDIRS := src +CXXFLAGS += -fno-rtti diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino b/pixelart-controller/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino new file mode 100644 index 0000000..f97f142 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino @@ -0,0 +1,47 @@ +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include "ESPAsyncWebServer.h" + +DNSServer dnsServer; +AsyncWebServer server(80); + +class CaptiveRequestHandler : public AsyncWebHandler { +public: + CaptiveRequestHandler() {} + virtual ~CaptiveRequestHandler() {} + + bool canHandle(AsyncWebServerRequest *request){ + //request->addInterestingHeader("ANY"); + return true; + } + + void handleRequest(AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("text/html"); + response->print("Captive Portal"); + response->print("

This is out captive portal front page.

"); + response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); + response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); + response->print(""); + request->send(response); + } +}; + + +void setup(){ + //your other setup stuff... + WiFi.softAP("esp-captive"); + dnsServer.start(53, "*", WiFi.softAPIP()); + server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP + //more handlers... + server.begin(); +} + +void loop(){ + dnsServer.processNextRequest(); +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino new file mode 100644 index 0000000..bf42d00 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino @@ -0,0 +1,221 @@ +#include +#ifdef ESP32 +#include +#include +#include +#include +#include +#elif defined(ESP8266) +#include +#include +#include +#endif +#include +#include + +// SKETCH BEGIN +AsyncWebServer server(80); +AsyncWebSocket ws("/ws"); +AsyncEventSource events("/events"); + +void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(type == WS_EVT_CONNECT){ + Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); + client->printf("Hello Client %u :)", client->id()); + client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id()); + } else if(type == WS_EVT_ERROR){ + Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); + } else if(type == WS_EVT_PONG){ + Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); + } else if(type == WS_EVT_DATA){ + AwsFrameInfo * info = (AwsFrameInfo*)arg; + String msg = ""; + if(info->final && info->index == 0 && info->len == len){ + //the whole message is in a single frame and we got all of it's data + Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); + + if(info->opcode == WS_TEXT){ + for(size_t i=0; i < info->len; i++) { + msg += (char) data[i]; + } + } else { + char buff[3]; + for(size_t i=0; i < info->len; i++) { + sprintf(buff, "%02x ", (uint8_t) data[i]); + msg += buff ; + } + } + Serial.printf("%s\n",msg.c_str()); + + if(info->opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } else { + //message is comprised of multiple frames or the frame is split into multiple packets + if(info->index == 0){ + if(info->num == 0) + Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); + } + + Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); + + if(info->opcode == WS_TEXT){ + for(size_t i=0; i < len; i++) { + msg += (char) data[i]; + } + } else { + char buff[3]; + for(size_t i=0; i < len; i++) { + sprintf(buff, "%02x ", (uint8_t) data[i]); + msg += buff ; + } + } + Serial.printf("%s\n",msg.c_str()); + + if((info->index + len) == info->len){ + Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); + if(info->final){ + Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + if(info->message_opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } + } + } + } +} + + +const char* ssid = "*******"; +const char* password = "*******"; +const char * hostName = "esp-async"; +const char* http_username = "admin"; +const char* http_password = "admin"; + +void setup(){ + Serial.begin(115200); + Serial.setDebugOutput(true); + WiFi.mode(WIFI_AP_STA); + WiFi.softAP(hostName); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("STA: Failed!\n"); + WiFi.disconnect(false); + delay(1000); + WiFi.begin(ssid, password); + } + + //Send OTA events to the browser + ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); }); + ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + char p[32]; + sprintf(p, "Progress: %u%%\n", (progress/(total/100))); + events.send(p, "ota"); + }); + ArduinoOTA.onError([](ota_error_t error) { + if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota"); + else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota"); + else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota"); + else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota"); + else if(error == OTA_END_ERROR) events.send("End Failed", "ota"); + }); + ArduinoOTA.setHostname(hostName); + ArduinoOTA.begin(); + + MDNS.addService("http","tcp",80); + + SPIFFS.begin(); + + ws.onEvent(onWsEvent); + server.addHandler(&ws); + + events.onConnect([](AsyncEventSourceClient *client){ + client->send("hello!",NULL,millis(),1000); + }); + server.addHandler(&events); + +#ifdef ESP32 + server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password)); +#elif defined(ESP8266) + server.addHandler(new SPIFFSEditor(http_username,http_password)); +#endif + + server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", String(ESP.getFreeHeap())); + }); + + server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm"); + + server.onNotFound([](AsyncWebServerRequest *request){ + Serial.printf("NOT_FOUND: "); + if(request->method() == HTTP_GET) + Serial.printf("GET"); + else if(request->method() == HTTP_POST) + Serial.printf("POST"); + else if(request->method() == HTTP_DELETE) + Serial.printf("DELETE"); + else if(request->method() == HTTP_PUT) + Serial.printf("PUT"); + else if(request->method() == HTTP_PATCH) + Serial.printf("PATCH"); + else if(request->method() == HTTP_HEAD) + Serial.printf("HEAD"); + else if(request->method() == HTTP_OPTIONS) + Serial.printf("OPTIONS"); + else + Serial.printf("UNKNOWN"); + Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str()); + + if(request->contentLength()){ + Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str()); + Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength()); + } + + int headers = request->headers(); + int i; + for(i=0;igetHeader(i); + Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); + } + + int params = request->params(); + for(i=0;igetParam(i); + if(p->isFile()){ + Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); + } else if(p->isPost()){ + Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } else { + Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } + } + + request->send(404); + }); + server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ + if(!index) + Serial.printf("UploadStart: %s\n", filename.c_str()); + Serial.printf("%s", (const char*)data); + if(final) + Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len); + }); + server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ + if(!index) + Serial.printf("BodyStart: %u\n", total); + Serial.printf("%s", (const char*)data); + if(index + len == total) + Serial.printf("BodyEnd: %u\n", total); + }); + server.begin(); +} + +void loop(){ + ArduinoOTA.handle(); + ws.cleanupClients(); +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/.exclude.files b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/.exclude.files new file mode 100644 index 0000000..955397f --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/.exclude.files @@ -0,0 +1,2 @@ +/*.js.gz +/.exclude.files diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ace.js.gz b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ace.js.gz new file mode 100644 index 0000000..7b175c1 Binary files /dev/null and b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ace.js.gz differ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz new file mode 100644 index 0000000..cf5b49f Binary files /dev/null and b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz differ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/favicon.ico b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/favicon.ico new file mode 100644 index 0000000..71b25fe Binary files /dev/null and b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/favicon.ico differ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/index.htm b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/index.htm new file mode 100644 index 0000000..28f47e9 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/index.htm @@ -0,0 +1,131 @@ + + + + + + WebSocketTester + + + + +

+    
+ $ +
+ + diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz new file mode 100644 index 0000000..ebd6fe9 Binary files /dev/null and b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz differ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz new file mode 100644 index 0000000..26b5353 Binary files /dev/null and b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz differ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz new file mode 100644 index 0000000..c0451c1 Binary files /dev/null and b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz differ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz new file mode 100644 index 0000000..ec8aa87 Binary files /dev/null and b/pixelart-controller/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz differ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/regex_patterns/.test.build_flags b/pixelart-controller/lib/ESPAsyncWebServer/examples/regex_patterns/.test.build_flags new file mode 100644 index 0000000..9ea3bb7 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/examples/regex_patterns/.test.build_flags @@ -0,0 +1 @@ +-DASYNCWEBSERVER_REGEX=1 diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/regex_patterns/regex_patterns.ino b/pixelart-controller/lib/ESPAsyncWebServer/examples/regex_patterns/regex_patterns.ino new file mode 100644 index 0000000..fb01306 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/examples/regex_patterns/regex_patterns.ino @@ -0,0 +1,77 @@ +// +// A simple server implementation with regex routes: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +// Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support + +// For platformio: platformio.ini: +// build_flags = +// -DASYNCWEBSERVER_REGEX + +// For arduino IDE: create/update platform.local.txt +// Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt +// Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt +// +// compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1 + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include + +AsyncWebServer server(80); + +const char* ssid = "YOUR_SSID"; +const char* password = "YOUR_PASSWORD"; + +const char* PARAM_MESSAGE = "message"; + +void notFound(AsyncWebServerRequest *request) { + request->send(404, "text/plain", "Not found"); +} + +void setup() { + + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", "Hello, world"); + }); + + // Send a GET request to /sensor/ + server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { + String sensorNumber = request->pathArg(0); + request->send(200, "text/plain", "Hello, sensor: " + sensorNumber); + }); + + // Send a GET request to /sensor//action/ + server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { + String sensorNumber = request->pathArg(0); + String action = request->pathArg(1); + request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action); + }); + + server.onNotFound(notFound); + + server.begin(); +} + +void loop() { +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/examples/simple_server/simple_server.ino b/pixelart-controller/lib/ESPAsyncWebServer/examples/simple_server/simple_server.ino new file mode 100644 index 0000000..bdbcf60 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/examples/simple_server/simple_server.ino @@ -0,0 +1,74 @@ +// +// A simple server implementation showing how to: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include + +AsyncWebServer server(80); + +const char* ssid = "YOUR_SSID"; +const char* password = "YOUR_PASSWORD"; + +const char* PARAM_MESSAGE = "message"; + +void notFound(AsyncWebServerRequest *request) { + request->send(404, "text/plain", "Not found"); +} + +void setup() { + + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", "Hello, world"); + }); + + // Send a GET request to /get?message= + server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) { + String message; + if (request->hasParam(PARAM_MESSAGE)) { + message = request->getParam(PARAM_MESSAGE)->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, GET: " + message); + }); + + // Send a POST request to /post with a form field message set to + server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){ + String message; + if (request->hasParam(PARAM_MESSAGE, true)) { + message = request->getParam(PARAM_MESSAGE, true)->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, POST: " + message); + }); + + server.onNotFound(notFound); + + server.begin(); +} + +void loop() { +} \ No newline at end of file diff --git a/pixelart-controller/lib/ESPAsyncWebServer/keywords.txt b/pixelart-controller/lib/ESPAsyncWebServer/keywords.txt new file mode 100644 index 0000000..c391e6c --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/keywords.txt @@ -0,0 +1,3 @@ +JsonArray KEYWORD1 +add KEYWORD2 +createArray KEYWORD3 diff --git a/pixelart-controller/lib/ESPAsyncWebServer/library.json b/pixelart-controller/lib/ESPAsyncWebServer/library.json new file mode 100644 index 0000000..750ca28 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/library.json @@ -0,0 +1,37 @@ +{ + "name":"ESP Async WebServer", + "description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32", + "keywords":"http,async,websocket,webserver", + "authors": + { + "name": "Hristo Gochkov", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/me-no-dev/ESPAsyncWebServer.git" + }, + "version": "1.2.3", + "license": "LGPL-3.0", + "frameworks": "arduino", + "platforms": ["espressif8266", "espressif32"], + "dependencies": [ + { + "owner": "me-no-dev", + "name": "ESPAsyncTCP", + "version": "^1.2.2", + "platforms": "espressif8266" + }, + { + "owner": "me-no-dev", + "name": "AsyncTCP", + "version": "^1.1.1", + "platforms": "espressif32" + }, + { + "name": "Hash", + "platforms": "espressif8266" + } + ] +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/library.properties b/pixelart-controller/lib/ESPAsyncWebServer/library.properties new file mode 100644 index 0000000..401b041 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/library.properties @@ -0,0 +1,9 @@ +name=ESP Async WebServer +version=1.2.3 +author=Me-No-Dev +maintainer=Me-No-Dev +sentence=Async Web Server for ESP8266 and ESP31B +paragraph=Async Web Server for ESP8266 and ESP31B +category=Other +url=https://github.com/me-no-dev/ESPAsyncWebServer +architectures=* diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp new file mode 100644 index 0000000..f2914df --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp @@ -0,0 +1,368 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "AsyncEventSource.h" + +static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = ""; + + if(reconnect){ + ev += "retry: "; + ev += String(reconnect); + ev += "\r\n"; + } + + if(id){ + ev += "id: "; + ev += String(id); + ev += "\r\n"; + } + + if(event != NULL){ + ev += "event: "; + ev += String(event); + ev += "\r\n"; + } + + if(message != NULL){ + size_t messageLen = strlen(message); + char * lineStart = (char *)message; + char * lineEnd; + do { + char * nextN = strchr(lineStart, '\n'); + char * nextR = strchr(lineStart, '\r'); + if(nextN == NULL && nextR == NULL){ + size_t llen = ((char *)message + messageLen) - lineStart; + char * ldata = (char *)malloc(llen+1); + if(ldata != NULL){ + memcpy(ldata, lineStart, llen); + ldata[llen] = 0; + ev += "data: "; + ev += ldata; + ev += "\r\n\r\n"; + free(ldata); + } + lineStart = (char *)message + messageLen; + } else { + char * nextLine = NULL; + if(nextN != NULL && nextR != NULL){ + if(nextR < nextN){ + lineEnd = nextR; + if(nextN == (nextR + 1)) + nextLine = nextN + 1; + else + nextLine = nextR + 1; + } else { + lineEnd = nextN; + if(nextR == (nextN + 1)) + nextLine = nextR + 1; + else + nextLine = nextN + 1; + } + } else if(nextN != NULL){ + lineEnd = nextN; + nextLine = nextN + 1; + } else { + lineEnd = nextR; + nextLine = nextR + 1; + } + + size_t llen = lineEnd - lineStart; + char * ldata = (char *)malloc(llen+1); + if(ldata != NULL){ + memcpy(ldata, lineStart, llen); + ldata[llen] = 0; + ev += "data: "; + ev += ldata; + ev += "\r\n"; + free(ldata); + } + lineStart = nextLine; + if(lineStart == ((char *)message + messageLen)) + ev += "\r\n"; + } + } while(lineStart < ((char *)message + messageLen)); + } + + return ev; +} + +// Message + +AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) +: _data(nullptr), _len(len), _sent(0), _acked(0) +{ + _data = (uint8_t*)malloc(_len+1); + if(_data == nullptr){ + _len = 0; + } else { + memcpy(_data, data, len); + _data[_len] = 0; + } +} + +AsyncEventSourceMessage::~AsyncEventSourceMessage() { + if(_data != NULL) + free(_data); +} + +size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { + (void)time; + // If the whole message is now acked... + if(_acked + len > _len){ + // Return the number of extra bytes acked (they will be carried on to the next message) + const size_t extra = _acked + len - _len; + _acked = _len; + return extra; + } + // Return that no extra bytes left. + _acked += len; + return 0; +} + +size_t AsyncEventSourceMessage::send(AsyncClient *client) { + const size_t len = _len - _sent; + if(client->space() < len){ + return 0; + } + size_t sent = client->add((const char *)_data, len); + if(client->canSend()) + client->send(); + _sent += sent; + return sent; +} + +// Client + +AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) +: _messageQueue(LinkedList([](AsyncEventSourceMessage *m){ delete m; })) +{ + _client = request->client(); + _server = server; + _lastId = 0; + if(request->hasHeader("Last-Event-ID")) + _lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str()); + + _client->setRxTimeout(0); + _client->onError(NULL, NULL); + _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); + _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); + _client->onData(NULL, NULL); + _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); + _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); + + _server->_addClient(this); + delete request; +} + +AsyncEventSourceClient::~AsyncEventSourceClient(){ + _messageQueue.free(); + close(); +} + +void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ + if(dataMessage == NULL) + return; + if(!connected()){ + delete dataMessage; + return; + } + if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ + ets_printf("ERROR: Too many messages queued\n"); + delete dataMessage; + } else { + _messageQueue.add(dataMessage); + } + if(_client->canSend()) + _runQueue(); +} + +void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ + while(len && !_messageQueue.isEmpty()){ + len = _messageQueue.front()->ack(len, time); + if(_messageQueue.front()->finished()) + _messageQueue.remove(_messageQueue.front()); + } + + _runQueue(); +} + +void AsyncEventSourceClient::_onPoll(){ + if(!_messageQueue.isEmpty()){ + _runQueue(); + } +} + + +void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ + _client->close(true); +} + +void AsyncEventSourceClient::_onDisconnect(){ + _client = NULL; + _server->_handleDisconnect(this); +} + +void AsyncEventSourceClient::close(){ + if(_client != NULL) + _client->close(); +} + +void AsyncEventSourceClient::write(const char * message, size_t len){ + _queueMessage(new AsyncEventSourceMessage(message, len)); +} + +void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = generateEventMessage(message, event, id, reconnect); + _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); +} + +void AsyncEventSourceClient::_runQueue(){ + while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){ + _messageQueue.remove(_messageQueue.front()); + } + + for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) + { + if(!(*i)->sent()) + (*i)->send(_client); + } +} + + +// Handler + +AsyncEventSource::AsyncEventSource(const String& url) + : _url(url) + , _clients(LinkedList([](AsyncEventSourceClient *c){ delete c; })) + , _connectcb(NULL) +{} + +AsyncEventSource::~AsyncEventSource(){ + close(); +} + +void AsyncEventSource::onConnect(ArEventHandlerFunction cb){ + _connectcb = cb; +} + +void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ + /*char * temp = (char *)malloc(2054); + if(temp != NULL){ + memset(temp+1,' ',2048); + temp[0] = ':'; + temp[2049] = '\r'; + temp[2050] = '\n'; + temp[2051] = '\r'; + temp[2052] = '\n'; + temp[2053] = 0; + client->write((const char *)temp, 2053); + free(temp); + }*/ + + _clients.add(client); + if(_connectcb) + _connectcb(client); +} + +void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ + _clients.remove(client); +} + +void AsyncEventSource::close(){ + for(const auto &c: _clients){ + if(c->connected()) + c->close(); + } +} + +// pmb fix +size_t AsyncEventSource::avgPacketsWaiting() const { + if(_clients.isEmpty()) + return 0; + + size_t aql=0; + uint32_t nConnectedClients=0; + + for(const auto &c: _clients){ + if(c->connected()) { + aql+=c->packetsWaiting(); + ++nConnectedClients; + } + } +// return aql / nConnectedClients; + return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up +} + +void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + + + String ev = generateEventMessage(message, event, id, reconnect); + for(const auto &c: _clients){ + if(c->connected()) { + c->write(ev.c_str(), ev.length()); + } + } +} + +size_t AsyncEventSource::count() const { + return _clients.count_if([](AsyncEventSourceClient *c){ + return c->connected(); + }); +} + +bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET || !request->url().equals(_url)) { + return false; + } + request->addInterestingHeader("Last-Event-ID"); + return true; +} + +void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + request->send(new AsyncEventSourceResponse(this)); +} + +// Response + +AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ + _server = server; + _code = 200; + _contentType = "text/event-stream"; + _sendContentLength = false; + addHeader("Cache-Control", "no-cache"); + addHeader("Connection","keep-alive"); +} + +void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ + String out = _assembleHead(request->version()); + request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; +} + +size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ + if(len){ + new AsyncEventSourceClient(request, _server); + } + return 0; +} + diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncEventSource.h b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncEventSource.h new file mode 100644 index 0000000..b097fa6 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncEventSource.h @@ -0,0 +1,133 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCEVENTSOURCE_H_ +#define ASYNCEVENTSOURCE_H_ + +#include +#ifdef ESP32 +#include +#define SSE_MAX_QUEUED_MESSAGES 32 +#else +#include +#define SSE_MAX_QUEUED_MESSAGES 8 +#endif +#include + +#include "AsyncWebSynchronization.h" + +#ifdef ESP8266 +#include +#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library +#include <../src/Hash.h> +#endif +#endif + +#ifdef ESP32 +#define DEFAULT_MAX_SSE_CLIENTS 8 +#else +#define DEFAULT_MAX_SSE_CLIENTS 4 +#endif + +class AsyncEventSource; +class AsyncEventSourceResponse; +class AsyncEventSourceClient; +typedef std::function ArEventHandlerFunction; + +class AsyncEventSourceMessage { + private: + uint8_t * _data; + size_t _len; + size_t _sent; + //size_t _ack; + size_t _acked; + public: + AsyncEventSourceMessage(const char * data, size_t len); + ~AsyncEventSourceMessage(); + size_t ack(size_t len, uint32_t time __attribute__((unused))); + size_t send(AsyncClient *client); + bool finished(){ return _acked == _len; } + bool sent() { return _sent == _len; } +}; + +class AsyncEventSourceClient { + private: + AsyncClient *_client; + AsyncEventSource *_server; + uint32_t _lastId; + LinkedList _messageQueue; + void _queueMessage(AsyncEventSourceMessage *dataMessage); + void _runQueue(); + + public: + + AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); + ~AsyncEventSourceClient(); + + AsyncClient* client(){ return _client; } + void close(); + void write(const char * message, size_t len); + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + bool connected() const { return (_client != NULL) && _client->connected(); } + uint32_t lastId() const { return _lastId; } + size_t packetsWaiting() const { return _messageQueue.length(); } + + //system callbacks (do not call) + void _onAck(size_t len, uint32_t time); + void _onPoll(); + void _onTimeout(uint32_t time); + void _onDisconnect(); +}; + +class AsyncEventSource: public AsyncWebHandler { + private: + String _url; + LinkedList _clients; + ArEventHandlerFunction _connectcb; + public: + AsyncEventSource(const String& url); + ~AsyncEventSource(); + + const char * url() const { return _url.c_str(); } + void close(); + void onConnect(ArEventHandlerFunction cb); + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + size_t count() const; //number clinets connected + size_t avgPacketsWaiting() const; + + //system callbacks (do not call) + void _addClient(AsyncEventSourceClient * client); + void _handleDisconnect(AsyncEventSourceClient * client); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; +}; + +class AsyncEventSourceResponse: public AsyncWebServerResponse { + private: + String _content; + AsyncEventSource *_server; + public: + AsyncEventSourceResponse(AsyncEventSource *server); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + + +#endif /* ASYNCEVENTSOURCE_H_ */ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncJson.h b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncJson.h new file mode 100644 index 0000000..2fa6a2d --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncJson.h @@ -0,0 +1,254 @@ +// AsyncJson.h +/* + Async Response to use with ArduinoJson and AsyncWebServer + Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. + + Example of callback in use + + server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) { + + AsyncJsonResponse * response = new AsyncJsonResponse(); + JsonObject& root = response->getRoot(); + root["key1"] = "key number one"; + JsonObject& nested = root.createNestedObject("nested"); + nested["key1"] = "key number one"; + + response->setLength(); + request->send(response); + }); + + -------------------- + + Async Request to use with ArduinoJson and AsyncWebServer + Written by Arsène von Wyss (avonwyss) + + Example + + AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint"); + handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { + JsonObject& jsonObj = json.as(); + // ... + }); + server.addHandler(handler); + +*/ +#ifndef ASYNC_JSON_H_ +#define ASYNC_JSON_H_ +#include +#include +#include + +#if ARDUINOJSON_VERSION_MAJOR == 5 + #define ARDUINOJSON_5_COMPATIBILITY +#else + #ifndef DYNAMIC_JSON_DOCUMENT_SIZE + #define DYNAMIC_JSON_DOCUMENT_SIZE 1024 + #endif +#endif + +constexpr const char* JSON_MIMETYPE = "application/json"; + +/* + * Json Response + * */ + +class ChunkPrint : public Print { + private: + uint8_t* _destination; + size_t _to_skip; + size_t _to_write; + size_t _pos; + public: + ChunkPrint(uint8_t* destination, size_t from, size_t len) + : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} + virtual ~ChunkPrint(){} + size_t write(uint8_t c){ + if (_to_skip > 0) { + _to_skip--; + return 1; + } else if (_to_write > 0) { + _to_write--; + _destination[_pos++] = c; + return 1; + } + return 0; + } + size_t write(const uint8_t *buffer, size_t size) + { + return this->Print::write(buffer, size); + } +}; + +class AsyncJsonResponse: public AsyncAbstractResponse { + protected: + +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer _jsonBuffer; +#else + DynamicJsonDocument _jsonBuffer; +#endif + + JsonVariant _root; + bool _isValid; + + public: + +#ifdef ARDUINOJSON_5_COMPATIBILITY + AsyncJsonResponse(bool isArray=false): _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if(isArray) + _root = _jsonBuffer.createArray(); + else + _root = _jsonBuffer.createObject(); + } +#else + AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if(isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); + } +#endif + + ~AsyncJsonResponse() {} + JsonVariant & getRoot() { return _root; } + bool _sourceValid() const { return _isValid; } + size_t setLength() { + +#ifdef ARDUINOJSON_5_COMPATIBILITY + _contentLength = _root.measureLength(); +#else + _contentLength = measureJson(_root); +#endif + + if (_contentLength) { _isValid = true; } + return _contentLength; + } + + size_t getSize() { return _jsonBuffer.size(); } + + size_t _fillBuffer(uint8_t *data, size_t len){ + ChunkPrint dest(data, _sentLength, len); + +#ifdef ARDUINOJSON_5_COMPATIBILITY + _root.printTo( dest ) ; +#else + serializeJson(_root, dest); +#endif + return len; + } +}; + +class PrettyAsyncJsonResponse: public AsyncJsonResponse { +public: +#ifdef ARDUINOJSON_5_COMPATIBILITY + PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {} +#else + PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} +#endif + size_t setLength () { +#ifdef ARDUINOJSON_5_COMPATIBILITY + _contentLength = _root.measurePrettyLength (); +#else + _contentLength = measureJsonPretty(_root); +#endif + if (_contentLength) {_isValid = true;} + return _contentLength; + } + size_t _fillBuffer (uint8_t *data, size_t len) { + ChunkPrint dest (data, _sentLength, len); +#ifdef ARDUINOJSON_5_COMPATIBILITY + _root.prettyPrintTo (dest); +#else + serializeJsonPretty(_root, dest); +#endif + return len; + } +}; + +typedef std::function ArJsonRequestHandlerFunction; + +class AsyncCallbackJsonWebHandler: public AsyncWebHandler { +private: +protected: + const String _uri; + WebRequestMethodComposite _method; + ArJsonRequestHandlerFunction _onRequest; + size_t _contentLength; +#ifndef ARDUINOJSON_5_COMPATIBILITY + const size_t maxJsonBufferSize; +#endif + size_t _maxContentLength; +public: +#ifdef ARDUINOJSON_5_COMPATIBILITY + AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) + : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} +#else + AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE) + : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} +#endif + + void setMethod(WebRequestMethodComposite method){ _method = method; } + void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; } + void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; } + + virtual bool canHandle(AsyncWebServerRequest *request) override final{ + if(!_onRequest) + return false; + + if(!(_method & request->method())) + return false; + + if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) + return false; + + if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) ) + return false; + + request->addInterestingHeader("ANY"); + return true; + } + + virtual void handleRequest(AsyncWebServerRequest *request) override final { + if(_onRequest) { + if (request->_tempObject != NULL) { + +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer jsonBuffer; + JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); + if (json.success()) { +#else + DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); + if(!error) { + JsonVariant json = jsonBuffer.as(); +#endif + + _onRequest(request, json); + return; + } + } + request->send(_contentLength > _maxContentLength ? 413 : 400); + } else { + request->send(500); + } + } + virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { + } + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { + if (_onRequest) { + _contentLength = total; + if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { + request->_tempObject = malloc(total); + } + if (request->_tempObject != NULL) { + memcpy((uint8_t*)(request->_tempObject) + index, data, len); + } + } + } + virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} +}; +#endif diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp new file mode 100644 index 0000000..f76f2fc --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp @@ -0,0 +1,1294 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "AsyncWebSocket.h" + +#include + +#ifndef ESP8266 +#include "mbedtls/sha1.h" +#else +#include +#endif + +#define MAX_PRINTF_LEN 64 + +size_t webSocketSendFrameWindow(AsyncClient *client){ + if(!client->canSend()) + return 0; + size_t space = client->space(); + if(space < 9) + return 0; + return space - 8; +} + +size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){ + if(!client->canSend()) + return 0; + size_t space = client->space(); + if(space < 2) + return 0; + uint8_t mbuf[4] = {0,0,0,0}; + uint8_t headLen = 2; + if(len && mask){ + headLen += 4; + mbuf[0] = rand() % 0xFF; + mbuf[1] = rand() % 0xFF; + mbuf[2] = rand() % 0xFF; + mbuf[3] = rand() % 0xFF; + } + if(len > 125) + headLen += 2; + if(space < headLen) + return 0; + space -= headLen; + + if(len > space) len = space; + + uint8_t *buf = (uint8_t*)malloc(headLen); + if(buf == NULL){ + //os_printf("could not malloc %u bytes for frame header\n", headLen); + return 0; + } + + buf[0] = opcode & 0x0F; + if(final) + buf[0] |= 0x80; + if(len < 126) + buf[1] = len & 0x7F; + else { + buf[1] = 126; + buf[2] = (uint8_t)((len >> 8) & 0xFF); + buf[3] = (uint8_t)(len & 0xFF); + } + if(len && mask){ + buf[1] |= 0x80; + memcpy(buf + (headLen - 4), mbuf, 4); + } + if(client->add((const char *)buf, headLen) != headLen){ + //os_printf("error adding %lu header bytes\n", headLen); + free(buf); + return 0; + } + free(buf); + + if(len){ + if(len && mask){ + size_t i; + for(i=0;iadd((const char *)data, len) != len){ + //os_printf("error adding %lu data bytes\n", len); + return 0; + } + } + if(!client->send()){ + //os_printf("error sending frame: %lu\n", headLen+len); + return 0; + } + return len; +} + + +/* + * AsyncWebSocketMessageBuffer + */ + + + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer() + :_data(nullptr) + ,_len(0) + ,_lock(false) + ,_count(0) +{ + +} + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t size) + :_data(nullptr) + ,_len(size) + ,_lock(false) + ,_count(0) +{ + + if (!data) { + return; + } + + _data = new uint8_t[_len + 1]; + + if (_data) { + memcpy(_data, data, _len); + _data[_len] = 0; + } +} + + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) + :_data(nullptr) + ,_len(size) + ,_lock(false) + ,_count(0) +{ + _data = new uint8_t[_len + 1]; + + if (_data) { + _data[_len] = 0; + } + +} + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer & copy) + :_data(nullptr) + ,_len(0) + ,_lock(false) + ,_count(0) +{ + _len = copy._len; + _lock = copy._lock; + _count = 0; + + if (_len) { + _data = new uint8_t[_len + 1]; + _data[_len] = 0; + } + + if (_data) { + memcpy(_data, copy._data, _len); + _data[_len] = 0; + } + +} + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer && copy) + :_data(nullptr) + ,_len(0) + ,_lock(false) + ,_count(0) +{ + _len = copy._len; + _lock = copy._lock; + _count = 0; + + if (copy._data) { + _data = copy._data; + copy._data = nullptr; + } + +} + +AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() +{ + if (_data) { + delete[] _data; + } +} + +bool AsyncWebSocketMessageBuffer::reserve(size_t size) +{ + _len = size; + + if (_data) { + delete[] _data; + _data = nullptr; + } + + _data = new uint8_t[_len + 1]; + + if (_data) { + _data[_len] = 0; + return true; + } else { + return false; + } + +} + + + +/* + * Control Frame + */ + +class AsyncWebSocketControl { + private: + uint8_t _opcode; + uint8_t *_data; + size_t _len; + bool _mask; + bool _finished; + public: + AsyncWebSocketControl(uint8_t opcode, uint8_t *data=NULL, size_t len=0, bool mask=false) + :_opcode(opcode) + ,_len(len) + ,_mask(len && mask) + ,_finished(false) + { + if(data == NULL) + _len = 0; + if(_len){ + if(_len > 125) + _len = 125; + _data = (uint8_t*)malloc(_len); + if(_data == NULL) + _len = 0; + else memcpy(_data, data, len); + } else _data = NULL; + } + virtual ~AsyncWebSocketControl(){ + if(_data != NULL) + free(_data); + } + virtual bool finished() const { return _finished; } + uint8_t opcode(){ return _opcode; } + uint8_t len(){ return _len + 2; } + size_t send(AsyncClient *client){ + _finished = true; + return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len); + } +}; + +/* + * Basic Buffered Message + */ + + +AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode, bool mask) + :_len(len) + ,_sent(0) + ,_ack(0) + ,_acked(0) +{ + _opcode = opcode & 0x07; + _mask = mask; + _data = (uint8_t*)malloc(_len+1); + if(_data == NULL){ + _len = 0; + _status = WS_MSG_ERROR; + } else { + _status = WS_MSG_SENDING; + memcpy(_data, data, _len); + _data[_len] = 0; + } +} +AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask) + :_len(0) + ,_sent(0) + ,_ack(0) + ,_acked(0) + ,_data(NULL) +{ + _opcode = opcode & 0x07; + _mask = mask; + +} + + +AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() { + if(_data != NULL) + free(_data); +} + + void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) { + (void)time; + _acked += len; + if(_sent == _len && _acked == _ack){ + _status = WS_MSG_SENT; + } +} + size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) { + if(_status != WS_MSG_SENDING) + return 0; + if(_acked < _ack){ + return 0; + } + if(_sent == _len){ + if(_acked == _ack) + _status = WS_MSG_SENT; + return 0; + } + if(_sent > _len){ + _status = WS_MSG_ERROR; + return 0; + } + + size_t toSend = _len - _sent; + size_t window = webSocketSendFrameWindow(client); + + if(window < toSend) { + toSend = window; + } + + _sent += toSend; + _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4); + + bool final = (_sent == _len); + uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend)); + uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION; + + size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend); + _status = WS_MSG_SENDING; + if(toSend && sent != toSend){ + _sent -= (toSend - sent); + _ack -= (toSend - sent); + } + return sent; +} + +// bool AsyncWebSocketBasicMessage::reserve(size_t size) { +// if (size) { +// _data = (uint8_t*)malloc(size +1); +// if (_data) { +// memset(_data, 0, size); +// _len = size; +// _status = WS_MSG_SENDING; +// return true; +// } +// } +// return false; +// } + + +/* + * AsyncWebSocketMultiMessage Message + */ + + +AsyncWebSocketMultiMessage::AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode, bool mask) + :_len(0) + ,_sent(0) + ,_ack(0) + ,_acked(0) + ,_WSbuffer(nullptr) +{ + + _opcode = opcode & 0x07; + _mask = mask; + + if (buffer) { + _WSbuffer = buffer; + (*_WSbuffer)++; + _data = buffer->get(); + _len = buffer->length(); + _status = WS_MSG_SENDING; + //ets_printf("M: %u\n", _len); + } else { + _status = WS_MSG_ERROR; + } + +} + + +AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() { + if (_WSbuffer) { + (*_WSbuffer)--; // decreases the counter. + } +} + + void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) { + (void)time; + _acked += len; + if(_sent >= _len && _acked >= _ack){ + _status = WS_MSG_SENT; + } + //ets_printf("A: %u\n", len); +} + size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) { + if(_status != WS_MSG_SENDING) + return 0; + if(_acked < _ack){ + return 0; + } + if(_sent == _len){ + _status = WS_MSG_SENT; + return 0; + } + if(_sent > _len){ + _status = WS_MSG_ERROR; + //ets_printf("E: %u > %u\n", _sent, _len); + return 0; + } + + size_t toSend = _len - _sent; + size_t window = webSocketSendFrameWindow(client); + + if(window < toSend) { + toSend = window; + } + + _sent += toSend; + _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4); + + //ets_printf("W: %u %u\n", _sent - toSend, toSend); + + bool final = (_sent == _len); + uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend)); + uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION; + + size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend); + _status = WS_MSG_SENDING; + if(toSend && sent != toSend){ + //ets_printf("E: %u != %u\n", toSend, sent); + _sent -= (toSend - sent); + _ack -= (toSend - sent); + } + //ets_printf("S: %u %u\n", _sent, sent); + return sent; +} + + +/* + * Async WebSocket Client + */ + const char * AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING"; + const size_t AWSC_PING_PAYLOAD_LEN = 22; + +AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) + : _controlQueue(LinkedList([](AsyncWebSocketControl *c){ delete c; })) + , _messageQueue(LinkedList([](AsyncWebSocketMessage *m){ delete m; })) + , _tempObject(NULL) +{ + _client = request->client(); + _server = server; + _clientId = _server->_getNextId(); + _status = WS_CONNECTED; + _pstate = 0; + _lastMessageTime = millis(); + _keepAlivePeriod = 0; + _client->setRxTimeout(0); + _client->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this); + _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); + _client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this); + _client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); + _client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); + _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); + _server->_addClient(this); + _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0); + delete request; +} + +AsyncWebSocketClient::~AsyncWebSocketClient(){ + _messageQueue.free(); + _controlQueue.free(); + _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); +} + +void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){ + _lastMessageTime = millis(); + if(!_controlQueue.isEmpty()){ + auto head = _controlQueue.front(); + if(head->finished()){ + len -= head->len(); + if(_status == WS_DISCONNECTING && head->opcode() == WS_DISCONNECT){ + _controlQueue.remove(head); + _status = WS_DISCONNECTED; + _client->close(true); + return; + } + _controlQueue.remove(head); + } + } + if(len && !_messageQueue.isEmpty()){ + _messageQueue.front()->ack(len, time); + } + _server->_cleanBuffers(); + _runQueue(); +} + +void AsyncWebSocketClient::_onPoll(){ + if(_client->canSend() && (!_controlQueue.isEmpty() || !_messageQueue.isEmpty())){ + _runQueue(); + } else if(_keepAlivePeriod > 0 && _controlQueue.isEmpty() && _messageQueue.isEmpty() && (millis() - _lastMessageTime) >= _keepAlivePeriod){ + ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN); + } +} + +void AsyncWebSocketClient::_runQueue(){ + while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){ + _messageQueue.remove(_messageQueue.front()); + } + + if(!_controlQueue.isEmpty() && (_messageQueue.isEmpty() || _messageQueue.front()->betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front()->len() - 1)){ + _controlQueue.front()->send(_client); + } else if(!_messageQueue.isEmpty() && _messageQueue.front()->betweenFrames() && webSocketSendFrameWindow(_client)){ + _messageQueue.front()->send(_client); + } +} + +bool AsyncWebSocketClient::queueIsFull(){ + if((_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED) ) return true; + return false; +} + +void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){ + if(dataMessage == NULL) + return; + if(_status != WS_CONNECTED){ + delete dataMessage; + return; + } + if(_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES){ + ets_printf("ERROR: Too many messages queued\n"); + delete dataMessage; + } else { + _messageQueue.add(dataMessage); + } + if(_client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage){ + if(controlMessage == NULL) + return; + _controlQueue.add(controlMessage); + if(_client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::close(uint16_t code, const char * message){ + if(_status != WS_CONNECTED) + return; + if(code){ + uint8_t packetLen = 2; + if(message != NULL){ + size_t mlen = strlen(message); + if(mlen > 123) mlen = 123; + packetLen += mlen; + } + char * buf = (char*)malloc(packetLen); + if(buf != NULL){ + buf[0] = (uint8_t)(code >> 8); + buf[1] = (uint8_t)(code & 0xFF); + if(message != NULL){ + memcpy(buf+2, message, packetLen -2); + } + _queueControl(new AsyncWebSocketControl(WS_DISCONNECT,(uint8_t*)buf,packetLen)); + free(buf); + return; + } + } + _queueControl(new AsyncWebSocketControl(WS_DISCONNECT)); +} + +void AsyncWebSocketClient::ping(uint8_t *data, size_t len){ + if(_status == WS_CONNECTED) + _queueControl(new AsyncWebSocketControl(WS_PING, data, len)); +} + +void AsyncWebSocketClient::_onError(int8_t){} + +void AsyncWebSocketClient::_onTimeout(uint32_t time){ + (void)time; + _client->close(true); +} + +void AsyncWebSocketClient::_onDisconnect(){ + _client = NULL; + _server->_handleDisconnect(this); +} + +void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){ + _lastMessageTime = millis(); + uint8_t *data = (uint8_t*)pbuf; + while(plen > 0){ + if(!_pstate){ + const uint8_t *fdata = data; + _pinfo.index = 0; + _pinfo.final = (fdata[0] & 0x80) != 0; + _pinfo.opcode = fdata[0] & 0x0F; + _pinfo.masked = (fdata[1] & 0x80) != 0; + _pinfo.len = fdata[1] & 0x7F; + data += 2; + plen -= 2; + if(_pinfo.len == 126){ + _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8; + data += 2; + plen -= 2; + } else if(_pinfo.len == 127){ + _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; + data += 8; + plen -= 8; + } + + if(_pinfo.masked){ + memcpy(_pinfo.mask, data, 4); + data += 4; + plen -= 4; + } + } + + const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen); + const auto datalast = data[datalen]; + + if(_pinfo.masked){ + for(size_t i=0;i_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, datalen); + + _pinfo.index += datalen; + } else if((datalen + _pinfo.index) == _pinfo.len){ + _pstate = 0; + if(_pinfo.opcode == WS_DISCONNECT){ + if(datalen){ + uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1]; + char * reasonString = (char*)(data+2); + if(reasonCode > 1001){ + _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString)); + } + } + if(_status == WS_DISCONNECTING){ + _status = WS_DISCONNECTED; + _client->close(true); + } else { + _status = WS_DISCONNECTING; + _client->ackLater(); + _queueControl(new AsyncWebSocketControl(WS_DISCONNECT, data, datalen)); + } + } else if(_pinfo.opcode == WS_PING){ + _queueControl(new AsyncWebSocketControl(WS_PONG, data, datalen)); + } else if(_pinfo.opcode == WS_PONG){ + if(datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) + _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen); + } else if(_pinfo.opcode < 8){//continuation or text/binary frame + _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); + } + } else { + //os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len); + //what should we do? + break; + } + + // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0; + if (datalen > 0) + data[datalen] = datalast; + + data += datalen; + plen -= datalen; + } +} + +size_t AsyncWebSocketClient::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + va_end(arg); + return 0; + } + char* buffer = temp; + size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); + va_end(arg); + + if (len > (MAX_PRINTF_LEN - 1)) { + buffer = new char[len + 1]; + if (!buffer) { + delete[] temp; + return 0; + } + va_start(arg, format); + vsnprintf(buffer, len + 1, format, arg); + va_end(arg); + } + text(buffer, len); + if (buffer != temp) { + delete[] buffer; + } + delete[] temp; + return len; +} + +#ifndef ESP32 +size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) { + va_list arg; + va_start(arg, formatP); + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + va_end(arg); + return 0; + } + char* buffer = temp; + size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); + va_end(arg); + + if (len > (MAX_PRINTF_LEN - 1)) { + buffer = new char[len + 1]; + if (!buffer) { + delete[] temp; + return 0; + } + va_start(arg, formatP); + vsnprintf_P(buffer, len + 1, formatP, arg); + va_end(arg); + } + text(buffer, len); + if (buffer != temp) { + delete[] buffer; + } + delete[] temp; + return len; +} +#endif + +void AsyncWebSocketClient::text(const char * message, size_t len){ + _queueMessage(new AsyncWebSocketBasicMessage(message, len)); +} +void AsyncWebSocketClient::text(const char * message){ + text(message, strlen(message)); +} +void AsyncWebSocketClient::text(uint8_t * message, size_t len){ + text((const char *)message, len); +} +void AsyncWebSocketClient::text(char * message){ + text(message, strlen(message)); +} +void AsyncWebSocketClient::text(const String &message){ + text(message.c_str(), message.length()); +} +void AsyncWebSocketClient::text(const __FlashStringHelper *data){ + PGM_P p = reinterpret_cast(data); + size_t n = 0; + while (1) { + if (pgm_read_byte(p+n) == 0) break; + n += 1; + } + char * message = (char*) malloc(n+1); + if(message){ + for(size_t b=0; b(data); + char * message = (char*) malloc(len); + if(message){ + for(size_t b=0; bremoteIP(); +} + +uint16_t AsyncWebSocketClient::remotePort() { + if(!_client) { + return 0; + } + return _client->remotePort(); +} + + + +/* + * Async Web Socket - Each separate socket location + */ + +AsyncWebSocket::AsyncWebSocket(const String& url) + :_url(url) + ,_clients(LinkedList([](AsyncWebSocketClient *c){ delete c; })) + ,_cNextId(1) + ,_enabled(true) + ,_buffers(LinkedList([](AsyncWebSocketMessageBuffer *b){ delete b; })) +{ + _eventHandler = NULL; +} + +AsyncWebSocket::~AsyncWebSocket(){} + +void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(_eventHandler != NULL){ + _eventHandler(this, client, type, arg, data, len); + } +} + +void AsyncWebSocket::_addClient(AsyncWebSocketClient * client){ + _clients.add(client); +} + +void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client){ + + _clients.remove_first([=](AsyncWebSocketClient * c){ + return c->id() == client->id(); + }); +} + +bool AsyncWebSocket::availableForWriteAll(){ + for(const auto& c: _clients){ + if(c->queueIsFull()) return false; + } + return true; +} + +bool AsyncWebSocket::availableForWrite(uint32_t id){ + for(const auto& c: _clients){ + if(c->queueIsFull() && (c->id() == id )) return false; + } + return true; +} + +size_t AsyncWebSocket::count() const { + return _clients.count_if([](AsyncWebSocketClient * c){ + return c->status() == WS_CONNECTED; + }); +} + +AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){ + for(const auto &c: _clients){ + if(c->id() == id && c->status() == WS_CONNECTED){ + return c; + } + } + return nullptr; +} + + +void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message){ + AsyncWebSocketClient * c = client(id); + if(c) + c->close(code, message); +} + +void AsyncWebSocket::closeAll(uint16_t code, const char * message){ + for(const auto& c: _clients){ + if(c->status() == WS_CONNECTED) + c->close(code, message); + } +} + +void AsyncWebSocket::cleanupClients(uint16_t maxClients) +{ + if (count() > maxClients){ + _clients.front()->close(); + } +} + +void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len){ + AsyncWebSocketClient * c = client(id); + if(c) + c->ping(data, len); +} + +void AsyncWebSocket::pingAll(uint8_t *data, size_t len){ + for(const auto& c: _clients){ + if(c->status() == WS_CONNECTED) + c->ping(data, len); + } +} + +void AsyncWebSocket::text(uint32_t id, const char * message, size_t len){ + AsyncWebSocketClient * c = client(id); + if(c) + c->text(message, len); +} + +void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer){ + if (!buffer) return; + buffer->lock(); + for(const auto& c: _clients){ + if(c->status() == WS_CONNECTED){ + c->text(buffer); + } + } + buffer->unlock(); + _cleanBuffers(); +} + + +void AsyncWebSocket::textAll(const char * message, size_t len){ + AsyncWebSocketMessageBuffer * WSBuffer = makeBuffer((uint8_t *)message, len); + textAll(WSBuffer); +} + +void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len){ + AsyncWebSocketClient * c = client(id); + if(c) + c->binary(message, len); +} + +void AsyncWebSocket::binaryAll(const char * message, size_t len){ + AsyncWebSocketMessageBuffer * buffer = makeBuffer((uint8_t *)message, len); + binaryAll(buffer); +} + +void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) +{ + if (!buffer) return; + buffer->lock(); + for(const auto& c: _clients){ + if(c->status() == WS_CONNECTED) + c->binary(buffer); + } + buffer->unlock(); + _cleanBuffers(); +} + +void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage *message){ + AsyncWebSocketClient * c = client(id); + if(c) + c->message(message); +} + +void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage *message){ + for(const auto& c: _clients){ + if(c->status() == WS_CONNECTED) + c->message(message); + } + _cleanBuffers(); +} + +size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){ + AsyncWebSocketClient * c = client(id); + if(c){ + va_list arg; + va_start(arg, format); + size_t len = c->printf(format, arg); + va_end(arg); + return len; + } + return 0; +} + +size_t AsyncWebSocket::printfAll(const char *format, ...) { + va_list arg; + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + return 0; + } + va_start(arg, format); + size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); + va_end(arg); + delete[] temp; + + AsyncWebSocketMessageBuffer * buffer = makeBuffer(len); + if (!buffer) { + return 0; + } + + va_start(arg, format); + vsnprintf( (char *)buffer->get(), len + 1, format, arg); + va_end(arg); + + textAll(buffer); + return len; +} + +#ifndef ESP32 +size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...){ + AsyncWebSocketClient * c = client(id); + if(c != NULL){ + va_list arg; + va_start(arg, formatP); + size_t len = c->printf_P(formatP, arg); + va_end(arg); + return len; + } + return 0; +} +#endif + +size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) { + va_list arg; + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + return 0; + } + va_start(arg, formatP); + size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); + va_end(arg); + delete[] temp; + + AsyncWebSocketMessageBuffer * buffer = makeBuffer(len + 1); + if (!buffer) { + return 0; + } + + va_start(arg, formatP); + vsnprintf_P((char *)buffer->get(), len + 1, formatP, arg); + va_end(arg); + + textAll(buffer); + return len; +} + +void AsyncWebSocket::text(uint32_t id, const char * message){ + text(id, message, strlen(message)); +} +void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len){ + text(id, (const char *)message, len); +} +void AsyncWebSocket::text(uint32_t id, char * message){ + text(id, message, strlen(message)); +} +void AsyncWebSocket::text(uint32_t id, const String &message){ + text(id, message.c_str(), message.length()); +} +void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *message){ + AsyncWebSocketClient * c = client(id); + if(c != NULL) + c->text(message); +} +void AsyncWebSocket::textAll(const char * message){ + textAll(message, strlen(message)); +} +void AsyncWebSocket::textAll(uint8_t * message, size_t len){ + textAll((const char *)message, len); +} +void AsyncWebSocket::textAll(char * message){ + textAll(message, strlen(message)); +} +void AsyncWebSocket::textAll(const String &message){ + textAll(message.c_str(), message.length()); +} +void AsyncWebSocket::textAll(const __FlashStringHelper *message){ + for(const auto& c: _clients){ + if(c->status() == WS_CONNECTED) + c->text(message); + } +} +void AsyncWebSocket::binary(uint32_t id, const char * message){ + binary(id, message, strlen(message)); +} +void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len){ + binary(id, (const char *)message, len); +} +void AsyncWebSocket::binary(uint32_t id, char * message){ + binary(id, message, strlen(message)); +} +void AsyncWebSocket::binary(uint32_t id, const String &message){ + binary(id, message.c_str(), message.length()); +} +void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *message, size_t len){ + AsyncWebSocketClient * c = client(id); + if(c != NULL) + c-> binary(message, len); +} +void AsyncWebSocket::binaryAll(const char * message){ + binaryAll(message, strlen(message)); +} +void AsyncWebSocket::binaryAll(uint8_t * message, size_t len){ + binaryAll((const char *)message, len); +} +void AsyncWebSocket::binaryAll(char * message){ + binaryAll(message, strlen(message)); +} +void AsyncWebSocket::binaryAll(const String &message){ + binaryAll(message.c_str(), message.length()); +} +void AsyncWebSocket::binaryAll(const __FlashStringHelper *message, size_t len){ + for(const auto& c: _clients){ + if(c->status() == WS_CONNECTED) + c-> binary(message, len); + } + } + +const char * WS_STR_CONNECTION = "Connection"; +const char * WS_STR_UPGRADE = "Upgrade"; +const char * WS_STR_ORIGIN = "Origin"; +const char * WS_STR_VERSION = "Sec-WebSocket-Version"; +const char * WS_STR_KEY = "Sec-WebSocket-Key"; +const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol"; +const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept"; +const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){ + if(!_enabled) + return false; + + if(request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS)) + return false; + + request->addInterestingHeader(WS_STR_CONNECTION); + request->addInterestingHeader(WS_STR_UPGRADE); + request->addInterestingHeader(WS_STR_ORIGIN); + request->addInterestingHeader(WS_STR_VERSION); + request->addInterestingHeader(WS_STR_KEY); + request->addInterestingHeader(WS_STR_PROTOCOL); + return true; +} + +void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){ + if(!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)){ + request->send(400); + return; + } + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())){ + return request->requestAuthentication(); + } + AsyncWebHeader* version = request->getHeader(WS_STR_VERSION); + if(version->value().toInt() != 13){ + AsyncWebServerResponse *response = request->beginResponse(400); + response->addHeader(WS_STR_VERSION,"13"); + request->send(response); + return; + } + AsyncWebHeader* key = request->getHeader(WS_STR_KEY); + AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this); + if(request->hasHeader(WS_STR_PROTOCOL)){ + AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL); + //ToDo: check protocol + response->addHeader(WS_STR_PROTOCOL, protocol->value()); + } + request->send(response); +} + +AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) +{ + AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size); + if (buffer) { + AsyncWebLockGuard l(_lock); + _buffers.add(buffer); + } + return buffer; +} + +AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size) +{ + AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size); + + if (buffer) { + AsyncWebLockGuard l(_lock); + _buffers.add(buffer); + } + + return buffer; +} + +void AsyncWebSocket::_cleanBuffers() +{ + AsyncWebLockGuard l(_lock); + + for(AsyncWebSocketMessageBuffer * c: _buffers){ + if(c && c->canDelete()){ + _buffers.remove(c); + } + } +} + +AsyncWebSocket::AsyncWebSocketClientLinkedList AsyncWebSocket::getClients() const { + return _clients; +} + +/* + * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server + * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480 + */ + +AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket *server){ + _server = server; + _code = 101; + _sendContentLength = false; + + uint8_t * hash = (uint8_t*)malloc(20); + if(hash == NULL){ + _state = RESPONSE_FAILED; + return; + } + char * buffer = (char *) malloc(33); + if(buffer == NULL){ + free(hash); + _state = RESPONSE_FAILED; + return; + } +#ifdef ESP8266 + sha1(key + WS_STR_UUID, hash); +#else + (String&)key += WS_STR_UUID; + mbedtls_sha1_context ctx; + mbedtls_sha1_init(&ctx); + mbedtls_sha1_starts_ret(&ctx); + mbedtls_sha1_update_ret(&ctx, (const unsigned char*)key.c_str(), key.length()); + mbedtls_sha1_finish_ret(&ctx, hash); + mbedtls_sha1_free(&ctx); +#endif + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *) hash, 20, buffer, &_state); + len = base64_encode_blockend((buffer + len), &_state); + addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE); + addHeader(WS_STR_UPGRADE, "websocket"); + addHeader(WS_STR_ACCEPT,buffer); + free(buffer); + free(hash); +} + +void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request){ + if(_state == RESPONSE_FAILED){ + request->client()->close(true); + return; + } + String out = _assembleHead(request->version()); + request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; +} + +size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + if(len){ + new AsyncWebSocketClient(request, _server); + } + return 0; +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSocket.h b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSocket.h new file mode 100644 index 0000000..5b03ace --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSocket.h @@ -0,0 +1,350 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSOCKET_H_ +#define ASYNCWEBSOCKET_H_ + +#include +#ifdef ESP32 +#include +#define WS_MAX_QUEUED_MESSAGES 32 +#else +#include +#define WS_MAX_QUEUED_MESSAGES 8 +#endif +#include + +#include "AsyncWebSynchronization.h" + +#ifdef ESP8266 +#include +#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library +#include <../src/Hash.h> +#endif +#endif + +#ifdef ESP32 +#define DEFAULT_MAX_WS_CLIENTS 8 +#else +#define DEFAULT_MAX_WS_CLIENTS 4 +#endif + +class AsyncWebSocket; +class AsyncWebSocketResponse; +class AsyncWebSocketClient; +class AsyncWebSocketControl; + +typedef struct { + /** Message type as defined by enum AwsFrameType. + * Note: Applications will only see WS_TEXT and WS_BINARY. + * All other types are handled by the library. */ + uint8_t message_opcode; + /** Frame number of a fragmented message. */ + uint32_t num; + /** Is this the last frame in a fragmented message ?*/ + uint8_t final; + /** Is this frame masked? */ + uint8_t masked; + /** Message type as defined by enum AwsFrameType. + * This value is the same as message_opcode for non-fragmented + * messages, but may also be WS_CONTINUATION in a fragmented message. */ + uint8_t opcode; + /** Length of the current frame. + * This equals the total length of the message if num == 0 && final == true */ + uint64_t len; + /** Mask key */ + uint8_t mask[4]; + /** Offset of the data inside the current frame. */ + uint64_t index; +} AwsFrameInfo; + +typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; +typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; +typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; +typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; + +class AsyncWebSocketMessageBuffer { + private: + uint8_t * _data; + size_t _len; + bool _lock; + uint32_t _count; + + public: + AsyncWebSocketMessageBuffer(); + AsyncWebSocketMessageBuffer(size_t size); + AsyncWebSocketMessageBuffer(uint8_t * data, size_t size); + AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &); + AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&); + ~AsyncWebSocketMessageBuffer(); + void operator ++(int i) { (void)i; _count++; } + void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; } + bool reserve(size_t size); + void lock() { _lock = true; } + void unlock() { _lock = false; } + uint8_t * get() { return _data; } + size_t length() { return _len; } + uint32_t count() { return _count; } + bool canDelete() { return (!_count && !_lock); } + + friend AsyncWebSocket; + +}; + +class AsyncWebSocketMessage { + protected: + uint8_t _opcode; + bool _mask; + AwsMessageStatus _status; + public: + AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){} + virtual ~AsyncWebSocketMessage(){} + virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){} + virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; } + virtual bool finished(){ return _status != WS_MSG_SENDING; } + virtual bool betweenFrames() const { return false; } +}; + +class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { + private: + size_t _len; + size_t _sent; + size_t _ack; + size_t _acked; + uint8_t * _data; +public: + AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false); + AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false); + virtual ~AsyncWebSocketBasicMessage() override; + virtual bool betweenFrames() const override { return _acked == _ack; } + virtual void ack(size_t len, uint32_t time) override ; + virtual size_t send(AsyncClient *client) override ; +}; + +class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage { + private: + uint8_t * _data; + size_t _len; + size_t _sent; + size_t _ack; + size_t _acked; + AsyncWebSocketMessageBuffer * _WSbuffer; +public: + AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false); + virtual ~AsyncWebSocketMultiMessage() override; + virtual bool betweenFrames() const override { return _acked == _ack; } + virtual void ack(size_t len, uint32_t time) override ; + virtual size_t send(AsyncClient *client) override ; +}; + +class AsyncWebSocketClient { + private: + AsyncClient *_client; + AsyncWebSocket *_server; + uint32_t _clientId; + AwsClientStatus _status; + + LinkedList _controlQueue; + LinkedList _messageQueue; + + uint8_t _pstate; + AwsFrameInfo _pinfo; + + uint32_t _lastMessageTime; + uint32_t _keepAlivePeriod; + + void _queueMessage(AsyncWebSocketMessage *dataMessage); + void _queueControl(AsyncWebSocketControl *controlMessage); + void _runQueue(); + + public: + void *_tempObject; + + AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); + ~AsyncWebSocketClient(); + + //client id increments for the given server + uint32_t id(){ return _clientId; } + AwsClientStatus status(){ return _status; } + AsyncClient* client(){ return _client; } + AsyncWebSocket *server(){ return _server; } + AwsFrameInfo const &pinfo() const { return _pinfo; } + + IPAddress remoteIP(); + uint16_t remotePort(); + + //control frames + void close(uint16_t code=0, const char * message=NULL); + void ping(uint8_t *data=NULL, size_t len=0); + + //set auto-ping period in seconds. disabled if zero (default) + void keepAlivePeriod(uint16_t seconds){ + _keepAlivePeriod = seconds * 1000; + } + uint16_t keepAlivePeriod(){ + return (uint16_t)(_keepAlivePeriod / 1000); + } + + //data packets + void message(AsyncWebSocketMessage *message){ _queueMessage(message); } + bool queueIsFull(); + + size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); +#ifndef ESP32 + size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); +#endif + void text(const char * message, size_t len); + void text(const char * message); + void text(uint8_t * message, size_t len); + void text(char * message); + void text(const String &message); + void text(const __FlashStringHelper *data); + void text(AsyncWebSocketMessageBuffer *buffer); + + void binary(const char * message, size_t len); + void binary(const char * message); + void binary(uint8_t * message, size_t len); + void binary(char * message); + void binary(const String &message); + void binary(const __FlashStringHelper *data, size_t len); + void binary(AsyncWebSocketMessageBuffer *buffer); + + bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; } + + //system callbacks (do not call) + void _onAck(size_t len, uint32_t time); + void _onError(int8_t); + void _onPoll(); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *pbuf, size_t plen); +}; + +typedef std::function AwsEventHandler; + +//WebServer Handler implementation that plays the role of a socket server +class AsyncWebSocket: public AsyncWebHandler { + public: + typedef LinkedList AsyncWebSocketClientLinkedList; + private: + String _url; + AsyncWebSocketClientLinkedList _clients; + uint32_t _cNextId; + AwsEventHandler _eventHandler; + bool _enabled; + AsyncWebLock _lock; + + public: + AsyncWebSocket(const String& url); + ~AsyncWebSocket(); + const char * url() const { return _url.c_str(); } + void enable(bool e){ _enabled = e; } + bool enabled() const { return _enabled; } + bool availableForWriteAll(); + bool availableForWrite(uint32_t id); + + size_t count() const; + AsyncWebSocketClient * client(uint32_t id); + bool hasClient(uint32_t id){ return client(id) != NULL; } + + void close(uint32_t id, uint16_t code=0, const char * message=NULL); + void closeAll(uint16_t code=0, const char * message=NULL); + void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); + + void ping(uint32_t id, uint8_t *data=NULL, size_t len=0); + void pingAll(uint8_t *data=NULL, size_t len=0); // done + + void text(uint32_t id, const char * message, size_t len); + void text(uint32_t id, const char * message); + void text(uint32_t id, uint8_t * message, size_t len); + void text(uint32_t id, char * message); + void text(uint32_t id, const String &message); + void text(uint32_t id, const __FlashStringHelper *message); + + void textAll(const char * message, size_t len); + void textAll(const char * message); + void textAll(uint8_t * message, size_t len); + void textAll(char * message); + void textAll(const String &message); + void textAll(const __FlashStringHelper *message); // need to convert + void textAll(AsyncWebSocketMessageBuffer * buffer); + + void binary(uint32_t id, const char * message, size_t len); + void binary(uint32_t id, const char * message); + void binary(uint32_t id, uint8_t * message, size_t len); + void binary(uint32_t id, char * message); + void binary(uint32_t id, const String &message); + void binary(uint32_t id, const __FlashStringHelper *message, size_t len); + + void binaryAll(const char * message, size_t len); + void binaryAll(const char * message); + void binaryAll(uint8_t * message, size_t len); + void binaryAll(char * message); + void binaryAll(const String &message); + void binaryAll(const __FlashStringHelper *message, size_t len); + void binaryAll(AsyncWebSocketMessageBuffer * buffer); + + void message(uint32_t id, AsyncWebSocketMessage *message); + void messageAll(AsyncWebSocketMultiMessage *message); + + size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); + size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); +#ifndef ESP32 + size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); +#endif + size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); + + //event listener + void onEvent(AwsEventHandler handler){ + _eventHandler = handler; + } + + //system callbacks (do not call) + uint32_t _getNextId(){ return _cNextId++; } + void _addClient(AsyncWebSocketClient * client); + void _handleDisconnect(AsyncWebSocketClient * client); + void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; + + + // messagebuffer functions/objects. + AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); + AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); + LinkedList _buffers; + void _cleanBuffers(); + + AsyncWebSocketClientLinkedList getClients() const; +}; + +//WebServer response to authenticate the socket and detach the tcp client from the web server request +class AsyncWebSocketResponse: public AsyncWebServerResponse { + private: + String _content; + AsyncWebSocket *_server; + public: + AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + + +#endif /* ASYNCWEBSOCKET_H_ */ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSynchronization.h b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSynchronization.h new file mode 100644 index 0000000..f36c52d --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/AsyncWebSynchronization.h @@ -0,0 +1,87 @@ +#ifndef ASYNCWEBSYNCHRONIZATION_H_ +#define ASYNCWEBSYNCHRONIZATION_H_ + +// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default + +#include + +#ifdef ESP32 + +// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore +class AsyncWebLock +{ +private: + SemaphoreHandle_t _lock; + mutable void *_lockedBy; + +public: + AsyncWebLock() { + _lock = xSemaphoreCreateBinary(); + _lockedBy = NULL; + xSemaphoreGive(_lock); + } + + ~AsyncWebLock() { + vSemaphoreDelete(_lock); + } + + bool lock() const { + extern void *pxCurrentTCB; + if (_lockedBy != pxCurrentTCB) { + xSemaphoreTake(_lock, portMAX_DELAY); + _lockedBy = pxCurrentTCB; + return true; + } + return false; + } + + void unlock() const { + _lockedBy = NULL; + xSemaphoreGive(_lock); + } +}; + +#else + +// This is the 8266 version of the Sync Lock which is currently unimplemented +class AsyncWebLock +{ + +public: + AsyncWebLock() { + } + + ~AsyncWebLock() { + } + + bool lock() const { + return false; + } + + void unlock() const { + } +}; +#endif + +class AsyncWebLockGuard +{ +private: + const AsyncWebLock *_lock; + +public: + AsyncWebLockGuard(const AsyncWebLock &l) { + if (l.lock()) { + _lock = &l; + } else { + _lock = NULL; + } + } + + ~AsyncWebLockGuard() { + if (_lock) { + _lock->unlock(); + } + } +}; + +#endif // ASYNCWEBSYNCHRONIZATION_H_ \ No newline at end of file diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h b/pixelart-controller/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h new file mode 100644 index 0000000..7cd21aa --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h @@ -0,0 +1,471 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _ESPAsyncWebServer_H_ +#define _ESPAsyncWebServer_H_ + +#include "Arduino.h" + +#include +#include "FS.h" + +#include "StringArray.h" + +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#else +#error Platform not supported +#endif + +#ifdef ASYNCWEBSERVER_REGEX +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE +#else +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) +#endif + +#define DEBUGF(...) //Serial.printf(__VA_ARGS__) + +class AsyncWebServer; +class AsyncWebServerRequest; +class AsyncWebServerResponse; +class AsyncWebHeader; +class AsyncWebParameter; +class AsyncWebRewrite; +class AsyncWebHandler; +class AsyncStaticWebHandler; +class AsyncCallbackWebHandler; +class AsyncResponseStream; + +#ifndef WEBSERVER_H +typedef enum { + HTTP_GET = 0b00000001, + HTTP_POST = 0b00000010, + HTTP_DELETE = 0b00000100, + HTTP_PUT = 0b00001000, + HTTP_PATCH = 0b00010000, + HTTP_HEAD = 0b00100000, + HTTP_OPTIONS = 0b01000000, + HTTP_ANY = 0b01111111, +} WebRequestMethod; +#endif + +//if this value is returned when asked for data, packet will not be sent and you will be asked for data again +#define RESPONSE_TRY_AGAIN 0xFFFFFFFF + +typedef uint8_t WebRequestMethodComposite; +typedef std::function ArDisconnectHandler; + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class AsyncWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + + AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} + const String& name() const { return _name; } + const String& value() const { return _value; } + size_t size() const { return _size; } + bool isPost() const { return _isForm; } + bool isFile() const { return _isFile; } +}; + +/* + * HEADER :: Chainable object to hold the headers + * */ + +class AsyncWebHeader { + private: + String _name; + String _value; + + public: + AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} + AsyncWebHeader(const String& data): _name(), _value(){ + if(!data) return; + int index = data.indexOf(':'); + if (index < 0) return; + _name = data.substring(0, index); + _value = data.substring(index + 2); + } + ~AsyncWebHeader(){} + const String& name() const { return _name; } + const String& value() const { return _value; } + String toString() const { return String(_name+": "+_value+"\r\n"); } +}; + +/* + * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect + * */ + +typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; + +typedef std::function AwsResponseFiller; +typedef std::function AwsTemplateProcessor; + +class AsyncWebServerRequest { + using File = fs::File; + using FS = fs::FS; + friend class AsyncWebServer; + friend class AsyncCallbackWebHandler; + private: + AsyncClient* _client; + AsyncWebServer* _server; + AsyncWebHandler* _handler; + AsyncWebServerResponse* _response; + StringArray _interestingHeaders; + ArDisconnectHandler _onDisconnectfn; + + String _temp; + uint8_t _parseState; + + uint8_t _version; + WebRequestMethodComposite _method; + String _url; + String _host; + String _contentType; + String _boundary; + String _authorization; + RequestedConnectionType _reqconntype; + void _removeNotInterestingHeaders(); + bool _isDigest; + bool _isMultipart; + bool _isPlainPost; + bool _expectingContinue; + size_t _contentLength; + size_t _parsedLength; + + LinkedList _headers; + LinkedList _params; + LinkedList _pathParams; + + uint8_t _multiParseState; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t *_itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + void _onPoll(); + void _onAck(size_t len, uint32_t time); + void _onError(int8_t error); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *buf, size_t len); + + void _addParam(AsyncWebParameter*); + void _addPathParam(const char *param); + + bool _parseReqHead(); + bool _parseReqHeader(); + void _parseLine(); + void _parsePlainPostChar(uint8_t data); + void _parseMultipartPostByte(uint8_t data, bool last); + void _addGetParams(const String& params); + + void _handleUploadStart(); + void _handleUploadByte(uint8_t data, bool last); + void _handleUploadEnd(); + + public: + File _tempFile; + void *_tempObject; + + AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); + ~AsyncWebServerRequest(); + + AsyncClient* client(){ return _client; } + uint8_t version() const { return _version; } + WebRequestMethodComposite method() const { return _method; } + const String& url() const { return _url; } + const String& host() const { return _host; } + const String& contentType() const { return _contentType; } + size_t contentLength() const { return _contentLength; } + bool multipart() const { return _isMultipart; } + const char * methodToString() const; + const char * requestedConnTypeToString() const; + RequestedConnectionType requestedConnType() const { return _reqconntype; } + bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); + void onDisconnect (ArDisconnectHandler fn); + + //hash is the string representation of: + // base64(user:pass) for basic or + // user:realm:md5(user:realm:pass) for digest + bool authenticate(const char * hash); + bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); + void requestAuthentication(const char * realm = NULL, bool isDigest = true); + + void setHandler(AsyncWebHandler *handler){ _handler = handler; } + void addInterestingHeader(const String& name); + + void redirect(const String& url); + + void send(AsyncWebServerResponse *response); + void send(int code, const String& contentType=String(), const String& content=String()); + void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); + AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + size_t headers() const; // get header count + bool hasHeader(const String& name) const; // check if header exists + bool hasHeader(const __FlashStringHelper * data) const; // check if header exists + + AsyncWebHeader* getHeader(const String& name) const; + AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; + AsyncWebHeader* getHeader(size_t num) const; + + size_t params() const; // get arguments count + bool hasParam(const String& name, bool post=false, bool file=false) const; + bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; + + AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; + AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; + AsyncWebParameter* getParam(size_t num) const; + + size_t args() const { return params(); } // get arguments count + const String& arg(const String& name) const; // get request argument value by name + const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) + const String& arg(size_t i) const; // get request argument value by number + const String& argName(size_t i) const; // get request argument name by number + bool hasArg(const char* name) const; // check if argument exists + bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists + + const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; + + const String& header(const char* name) const;// get request header value by name + const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) + const String& header(size_t i) const; // get request header value by number + const String& headerName(size_t i) const; // get request header name by number + String urlDecode(const String& text) const; +}; + +/* + * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) + * */ + +typedef std::function ArRequestFilterFunction; + +bool ON_STA_FILTER(AsyncWebServerRequest *request); + +bool ON_AP_FILTER(AsyncWebServerRequest *request); + +/* + * REWRITE :: One instance can be handle any Request (done by the Server) + * */ + +class AsyncWebRewrite { + protected: + String _from; + String _toUrl; + String _params; + ArRequestFilterFunction _filter; + public: + AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ + int index = _toUrl.indexOf('?'); + if (index > 0) { + _params = _toUrl.substring(index +1); + _toUrl = _toUrl.substring(0, index); + } + } + virtual ~AsyncWebRewrite(){} + AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } + const String& from(void) const { return _from; } + const String& toUrl(void) const { return _toUrl; } + const String& params(void) const { return _params; } + virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } +}; + +/* + * HANDLER :: One instance can be attached to any Request (done by the Server) + * */ + +class AsyncWebHandler { + protected: + ArRequestFilterFunction _filter; + String _username; + String _password; + public: + AsyncWebHandler():_username(""), _password(""){} + AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; + bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } + virtual ~AsyncWebHandler(){} + virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ + return false; + } + virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} + virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} + virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} + virtual bool isRequestHandlerTrivial(){return true;} +}; + +/* + * RESPONSE :: One instance is created for each Request (attached by the Handler) + * */ + +typedef enum { + RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED +} WebResponseState; + +class AsyncWebServerResponse { + protected: + int _code; + LinkedList _headers; + String _contentType; + size_t _contentLength; + bool _sendContentLength; + bool _chunked; + size_t _headLength; + size_t _sentLength; + size_t _ackedLength; + size_t _writtenLength; + WebResponseState _state; + const char* _responseCodeToString(int code); + + public: + AsyncWebServerResponse(); + virtual ~AsyncWebServerResponse(); + virtual void setCode(int code); + virtual void setContentLength(size_t len); + virtual void setContentType(const String& type); + virtual void addHeader(const String& name, const String& value); + virtual String _assembleHead(uint8_t version); + virtual bool _started() const; + virtual bool _finished() const; + virtual bool _failed() const; + virtual bool _sourceValid() const; + virtual void _respond(AsyncWebServerRequest *request); + virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +/* + * SERVER :: One instance + * */ + +typedef std::function ArRequestHandlerFunction; +typedef std::function ArUploadHandlerFunction; +typedef std::function ArBodyHandlerFunction; + +class AsyncWebServer { + protected: + AsyncServer _server; + LinkedList _rewrites; + LinkedList _handlers; + AsyncCallbackWebHandler* _catchAllHandler; + + public: + AsyncWebServer(uint16_t port); + ~AsyncWebServer(); + + void begin(); + void end(); + +#if ASYNC_TCP_SSL_ENABLED + void onSslFileRequest(AcSSlFileHandler cb, void* arg); + void beginSecure(const char *cert, const char *private_key_file, const char *password); +#endif + + AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); + bool removeRewrite(AsyncWebRewrite* rewrite); + AsyncWebRewrite& rewrite(const char* from, const char* to); + + AsyncWebHandler& addHandler(AsyncWebHandler* handler); + bool removeHandler(AsyncWebHandler* handler); + + AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); + + AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); + + void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned + void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads + void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) + + void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody + + void _handleDisconnect(AsyncWebServerRequest *request); + void _attachHandler(AsyncWebServerRequest *request); + void _rewriteRequest(AsyncWebServerRequest *request); +}; + +class DefaultHeaders { + using headers_t = LinkedList; + headers_t _headers; + + DefaultHeaders() + :_headers(headers_t([](AsyncWebHeader *h){ delete h; })) + {} +public: + using ConstIterator = headers_t::ConstIterator; + + void addHeader(const String& name, const String& value){ + _headers.add(new AsyncWebHeader(name, value)); + } + + ConstIterator begin() const { return _headers.begin(); } + ConstIterator end() const { return _headers.end(); } + + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders &operator=(DefaultHeaders const &) = delete; + static DefaultHeaders &Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +#include "WebResponseImpl.h" +#include "WebHandlerImpl.h" +#include "AsyncWebSocket.h" +#include "AsyncEventSource.h" + +#endif /* _AsyncWebServer_H_ */ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/SPIFFSEditor.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/SPIFFSEditor.cpp new file mode 100644 index 0000000..a84fa87 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/SPIFFSEditor.cpp @@ -0,0 +1,544 @@ +#include "SPIFFSEditor.h" +#include + +//File: edit.htm.gz, Size: 4151 +#define edit_htm_gz_len 4151 +const uint8_t edit_htm_gz[] PROGMEM = { + 0x1F, 0x8B, 0x08, 0x08, 0xB8, 0x94, 0xB1, 0x59, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68, + 0x74, 0x6D, 0x00, 0xB5, 0x3A, 0x0B, 0x7B, 0xDA, 0xB8, 0xB2, 0x7F, 0xC5, 0x71, 0xCF, 0x66, 0xED, + 0x83, 0x31, 0x90, 0xA4, 0xD9, 0xD6, 0xC4, 0xC9, 0x42, 0x92, 0x36, 0x6D, 0xF3, 0x6A, 0x80, 0xB6, + 0x69, 0x4F, 0xEE, 0x7E, 0xC2, 0x16, 0xA0, 0xC6, 0x96, 0x5D, 0x5B, 0x0E, 0x49, 0x59, 0xFE, 0xFB, + 0x9D, 0x91, 0x6C, 0xB0, 0x09, 0x69, 0x77, 0xCF, 0xBD, 0xBB, 0xDD, 0x2D, 0x92, 0x46, 0x33, 0x9A, + 0x19, 0xCD, 0x53, 0xDE, 0xBD, 0x8D, 0xA3, 0x8B, 0xC3, 0xFE, 0xF5, 0xE5, 0xB1, 0x36, 0x11, 0x61, + 0xB0, 0xBF, 0x87, 0x7F, 0x6B, 0x01, 0xE1, 0x63, 0x97, 0xF2, 0xFD, 0x3D, 0xC1, 0x44, 0x40, 0xF7, + 0x8F, 0x7B, 0x97, 0xDA, 0xB1, 0xCF, 0x44, 0x94, 0xEC, 0x35, 0xD4, 0xCA, 0x5E, 0x2A, 0x1E, 0x02, + 0xAA, 0x85, 0xD4, 0x67, 0xC4, 0x4D, 0xBD, 0x84, 0xC2, 0x66, 0xDB, 0x0B, 0x67, 0xDF, 0xEB, 0x8C, + 0xFB, 0xF4, 0xDE, 0xD9, 0x6E, 0x36, 0xDB, 0x71, 0x94, 0x32, 0xC1, 0x22, 0xEE, 0x90, 0x61, 0x1A, + 0x05, 0x99, 0xA0, 0xED, 0x80, 0x8E, 0x84, 0xF3, 0x3C, 0xBE, 0x6F, 0x0F, 0xA3, 0xC4, 0xA7, 0x89, + 0xD3, 0x8A, 0xEF, 0x35, 0x00, 0x31, 0x5F, 0x7B, 0xB6, 0xB3, 0xB3, 0xD3, 0x1E, 0x12, 0xEF, 0x76, + 0x9C, 0x44, 0x19, 0xF7, 0xEB, 0x5E, 0x14, 0x44, 0x89, 0xF3, 0x6C, 0xF4, 0x1C, 0xFF, 0xB4, 0x7D, + 0x96, 0xC6, 0x01, 0x79, 0x70, 0x78, 0xC4, 0x29, 0xE0, 0xDE, 0xD7, 0xD3, 0x09, 0xF1, 0xA3, 0xA9, + 0xD3, 0xD4, 0x9A, 0x5A, 0xAB, 0x09, 0x44, 0x92, 0xF1, 0x90, 0x18, 0x4D, 0x0B, 0xFF, 0xD8, 0x3B, + 0x66, 0x7B, 0x14, 0x71, 0x51, 0x4F, 0xD9, 0x77, 0xEA, 0xB4, 0xB6, 0xE0, 0x34, 0x39, 0x1D, 0x91, + 0x90, 0x05, 0x0F, 0x4E, 0x4A, 0x78, 0x5A, 0x4F, 0x69, 0xC2, 0x46, 0x6A, 0x79, 0x4A, 0xD9, 0x78, + 0x22, 0x9C, 0xDF, 0x9A, 0xCD, 0x39, 0xF0, 0xAF, 0x65, 0xC1, 0x2C, 0x60, 0x29, 0x20, 0xA3, 0x78, + 0xEA, 0x3C, 0x11, 0xC5, 0x4E, 0x53, 0xB1, 0xDE, 0x6C, 0x87, 0x24, 0x19, 0x33, 0x0E, 0x83, 0x98, + 0xF8, 0x3E, 0xE3, 0x63, 0x47, 0xA1, 0x05, 0x6C, 0xB6, 0x90, 0x36, 0xA1, 0x01, 0x11, 0xEC, 0x8E, + 0xB6, 0x43, 0xC6, 0xEB, 0x53, 0xE6, 0x8B, 0x89, 0xB3, 0x0B, 0x3C, 0xB6, 0xBD, 0x2C, 0x49, 0x41, + 0xA6, 0x38, 0x62, 0x5C, 0xD0, 0x44, 0xA2, 0xA5, 0x31, 0xE1, 0xB3, 0x5C, 0x54, 0x54, 0x40, 0x21, + 0x27, 0xE3, 0x01, 0xE3, 0xB4, 0x3E, 0x0C, 0x22, 0xEF, 0x76, 0x71, 0xD2, 0x6E, 0x7C, 0x9F, 0x9F, + 0xE5, 0x4C, 0xA2, 0x3B, 0x9A, 0xCC, 0x96, 0xEA, 0x92, 0xD8, 0x15, 0x60, 0x85, 0x34, 0xA5, 0x74, + 0x6E, 0x8B, 0xBB, 0x0C, 0xA0, 0x96, 0xFC, 0x05, 0x29, 0x17, 0xFC, 0x2F, 0x45, 0x5A, 0x11, 0x5C, + 0xA1, 0x30, 0x1E, 0x67, 0x62, 0xF6, 0xF8, 0x2A, 0xA3, 0x98, 0x78, 0x4C, 0x3C, 0xA0, 0xFC, 0xB0, + 0x6D, 0x86, 0xBA, 0x04, 0xAC, 0x24, 0x24, 0x81, 0x86, 0x3A, 0xD7, 0x3E, 0xD0, 0xC4, 0x27, 0x9C, + 0x58, 0x9D, 0x84, 0x91, 0xC0, 0xEA, 0x2D, 0xB5, 0x5E, 0x0F, 0xA3, 0xEF, 0xF5, 0x0C, 0xC6, 0x30, + 0x0F, 0xA8, 0x27, 0x94, 0x92, 0xE1, 0x1E, 0x86, 0xB7, 0x4C, 0x3C, 0x06, 0x3C, 0x5A, 0x28, 0xA9, + 0x4B, 0x2A, 0x69, 0xA2, 0x2E, 0xB0, 0x25, 0xD5, 0x83, 0x1C, 0x4B, 0xC9, 0x95, 0x50, 0xF5, 0x61, + 0x24, 0x44, 0x14, 0x4A, 0x93, 0x5B, 0x08, 0xAC, 0x49, 0xAB, 0x79, 0xF1, 0xE8, 0x46, 0xD6, 0x6B, + 0xBF, 0x44, 0xBE, 0x0D, 0x7A, 0x15, 0xCC, 0x23, 0x41, 0x9D, 0x04, 0x6C, 0xCC, 0x9D, 0x90, 0xF9, + 0x7E, 0x40, 0x4B, 0x56, 0xEB, 0x64, 0x49, 0x60, 0xF8, 0x44, 0x10, 0x87, 0x85, 0x64, 0x4C, 0x1B, + 0x31, 0x1F, 0x03, 0x34, 0xA5, 0xBB, 0x3B, 0x16, 0xFB, 0xD0, 0xBD, 0xB8, 0x9A, 0x36, 0xDF, 0xBD, + 0x1E, 0x47, 0x1D, 0xF8, 0xE7, 0xBC, 0x37, 0x98, 0x1C, 0x0F, 0xC6, 0x30, 0xEA, 0xE2, 0xB4, 0xF3, + 0xFE, 0xB0, 0xF3, 0x1E, 0x7E, 0x0E, 0x5B, 0xB5, 0xAF, 0xA3, 0x6F, 0xB8, 0xD0, 0x7D, 0xED, 0x77, + 0xFB, 0x83, 0xE3, 0x4E, 0xE7, 0x5D, 0xE3, 0xCD, 0xF9, 0xF4, 0xE3, 0xBB, 0x5D, 0x04, 0x77, 0x83, + 0xE6, 0xD5, 0x87, 0x49, 0x73, 0xB0, 0xF5, 0x32, 0xF4, 0x4F, 0xFC, 0x89, 0x17, 0x0E, 0x3A, 0xEF, + 0x3F, 0x5E, 0xDD, 0x5D, 0x87, 0x83, 0x71, 0xEF, 0x63, 0x6B, 0xF2, 0x79, 0xEB, 0x43, 0xEF, 0xF3, + 0xC7, 0x57, 0xB7, 0xF4, 0xD3, 0xC9, 0xDB, 0xCF, 0xFD, 0x29, 0x20, 0x1C, 0x45, 0xBD, 0xC1, 0x55, + 0xF7, 0x43, 0x77, 0xFC, 0xB9, 0xEB, 0x1D, 0xDF, 0x0F, 0x83, 0xF3, 0xEE, 0xEB, 0xCE, 0xB0, 0xB3, + 0xE5, 0x51, 0x3A, 0xEE, 0x5F, 0x75, 0xB3, 0x37, 0xEF, 0x2E, 0xC6, 0x8C, 0x4D, 0x7A, 0x9F, 0xCF, + 0xFB, 0xDE, 0xE1, 0xF3, 0xD3, 0xC1, 0x49, 0x87, 0x4D, 0xCE, 0xDF, 0x5E, 0x35, 0x6F, 0x5F, 0xBF, + 0x3B, 0x3C, 0xF2, 0xAE, 0xDF, 0x5E, 0xEF, 0x1E, 0x6D, 0x37, 0x7E, 0xFB, 0xED, 0xCC, 0xBF, 0x60, + 0xBC, 0x7F, 0xF7, 0xBD, 0x33, 0x3E, 0x9C, 0xBE, 0x78, 0x48, 0xFB, 0x93, 0x37, 0x77, 0xBC, 0xF1, + 0x21, 0xFA, 0xFA, 0xE6, 0xE1, 0x0C, 0xFE, 0xBB, 0xBC, 0xAC, 0x0D, 0x7B, 0xAD, 0x74, 0xF0, 0xFE, + 0xCD, 0x87, 0xAD, 0xF4, 0xE5, 0xF3, 0xB8, 0x7B, 0x74, 0x74, 0x17, 0x0E, 0x2F, 0x1B, 0xA1, 0x7F, + 0x3B, 0x12, 0x2F, 0xB6, 0x45, 0x7C, 0x3D, 0xCE, 0x3E, 0x7F, 0x7B, 0xFE, 0x76, 0xD2, 0xB8, 0xA0, + 0xE4, 0x7A, 0x52, 0x7B, 0xF8, 0xFE, 0xF0, 0x62, 0xD2, 0x3F, 0xB9, 0x3B, 0x0F, 0xC8, 0xFD, 0xF9, + 0xB9, 0xF7, 0x3D, 0xAC, 0x05, 0xE4, 0xE5, 0x45, 0x3F, 0x20, 0x49, 0x6B, 0xE0, 0x77, 0x1A, 0xB5, + 0xC3, 0xAD, 0xCE, 0x8E, 0x48, 0xAE, 0x0E, 0xF9, 0xD1, 0xF6, 0xD7, 0xDE, 0x8B, 0x6E, 0xB7, 0x15, + 0x0D, 0xBF, 0x6D, 0xBD, 0xBE, 0xDD, 0x7D, 0x3D, 0xD8, 0x7D, 0x3F, 0x7C, 0xDF, 0xE9, 0xED, 0x74, + 0x07, 0xE4, 0xBA, 0xF7, 0xBE, 0x33, 0xDA, 0x19, 0x4E, 0x26, 0xEF, 0xDE, 0xF5, 0x5F, 0xF9, 0x9D, + 0xEF, 0x49, 0xE7, 0x62, 0xDA, 0xB9, 0x3F, 0x1E, 0x74, 0x4E, 0x6A, 0xEF, 0x8E, 0xCF, 0x9A, 0xAD, + 0xDE, 0xF5, 0xF6, 0xF8, 0x6C, 0x77, 0xDA, 0x4D, 0x8F, 0x3B, 0xEF, 0xBB, 0xCD, 0xF1, 0xDB, 0x5A, + 0x48, 0x3E, 0x47, 0x87, 0xDB, 0xE3, 0x37, 0xBB, 0xEC, 0xF2, 0x9A, 0x74, 0xDE, 0x74, 0xDF, 0xA6, + 0xEC, 0x2A, 0x3C, 0x19, 0x34, 0x3B, 0x9D, 0xD3, 0x0B, 0xFA, 0xEA, 0x70, 0x9B, 0xBC, 0xDB, 0xF2, + 0x3E, 0x82, 0xFE, 0x07, 0x9F, 0xE8, 0x6F, 0xB5, 0xCE, 0xF4, 0xA2, 0x19, 0x78, 0x2F, 0x69, 0xFF, + 0xE4, 0xBA, 0x2F, 0x6F, 0xE7, 0x38, 0x78, 0xD5, 0xBF, 0xED, 0x65, 0xEF, 0xC3, 0xC3, 0x43, 0x53, + 0xE3, 0x51, 0x3D, 0xA1, 0x31, 0x25, 0xA2, 0x1C, 0xAE, 0x16, 0xFE, 0x01, 0xB6, 0xB5, 0xB4, 0xC2, + 0xDC, 0x4F, 0x05, 0xBD, 0x17, 0x75, 0x9F, 0x7A, 0x51, 0x42, 0xE4, 0x1E, 0x40, 0xA0, 0x09, 0x9A, + 0xD8, 0xFC, 0x77, 0x19, 0x3F, 0x35, 0x15, 0x3F, 0x35, 0xC2, 0x7D, 0xCD, 0x28, 0x1C, 0x01, 0x83, + 0x87, 0x4F, 0xEF, 0x98, 0x47, 0xEB, 0x31, 0xBB, 0xA7, 0x41, 0x5D, 0x22, 0x3B, 0x4D, 0x73, 0x26, + 0xFD, 0xAD, 0xD8, 0x46, 0x38, 0x98, 0x9A, 0xA4, 0x5A, 0x2C, 0xF8, 0x5F, 0x89, 0x47, 0x21, 0xB0, + 0x81, 0xCB, 0x84, 0xF8, 0xAB, 0x7C, 0x27, 0x4A, 0xEA, 0xC3, 0x6C, 0x3C, 0x62, 0xF7, 0xE0, 0xD0, + 0x23, 0xC6, 0x99, 0xA0, 0x5A, 0x2B, 0x9D, 0xFF, 0x5E, 0x90, 0xB9, 0xA5, 0x0F, 0xA3, 0x84, 0x84, + 0x34, 0xD5, 0xFE, 0x22, 0x99, 0xD9, 0x28, 0x89, 0xC2, 0x65, 0x10, 0x99, 0x8B, 0xA8, 0x34, 0x99, + 0xCF, 0x9F, 0x65, 0x71, 0x10, 0x11, 0x10, 0x73, 0x4D, 0xE4, 0x50, 0xF1, 0x34, 0x91, 0x6E, 0xB5, + 0x88, 0xAB, 0xB9, 0x9B, 0x6D, 0xA1, 0x5B, 0x96, 0xDD, 0x7A, 0x6B, 0x67, 0xE9, 0xBA, 0x75, 0xB9, + 0x17, 0xE3, 0xFD, 0x9A, 0x4C, 0x81, 0xF1, 0xA0, 0x14, 0xEE, 0x9E, 0x09, 0x50, 0xE9, 0x13, 0x87, + 0xCB, 0x43, 0xF2, 0xC8, 0xB0, 0x60, 0x40, 0x05, 0xEA, 0x96, 0x8C, 0xD4, 0x85, 0x24, 0xB0, 0x6F, + 0xFE, 0x8C, 0xCA, 0xBC, 0x67, 0x3D, 0x8B, 0x13, 0xB8, 0x0D, 0x3A, 0xFD, 0x11, 0xCD, 0x42, 0xA6, + 0x2A, 0x6D, 0x45, 0x53, 0x65, 0xBC, 0x5C, 0x84, 0x65, 0xDA, 0x93, 0xBC, 0x16, 0xA4, 0x1F, 0x4B, + 0x05, 0xE0, 0x05, 0x37, 0xCF, 0x91, 0x9B, 0x1F, 0x6A, 0x75, 0x7B, 0xF7, 0x97, 0x9C, 0x87, 0x9D, + 0xE6, 0x2F, 0x73, 0x3B, 0xDF, 0x5B, 0xA4, 0xE4, 0x56, 0x13, 0xFE, 0x29, 0x32, 0xEF, 0x8B, 0x25, + 0x0B, 0xC3, 0xE7, 0xF8, 0xA7, 0x60, 0x10, 0xE9, 0x94, 0x80, 0xDB, 0x3B, 0x2F, 0x5F, 0xF8, 0xC3, + 0x02, 0x98, 0x0B, 0xF6, 0x24, 0x3C, 0x21, 0x3E, 0xCB, 0x52, 0xE7, 0x79, 0xF3, 0x97, 0x5C, 0x9F, + 0x5B, 0x3B, 0x28, 0xFB, 0xE2, 0x2E, 0x71, 0xB2, 0xB4, 0xD8, 0x34, 0x66, 0x5C, 0xDB, 0x4A, 0x35, + 0xBC, 0x6F, 0x92, 0x2C, 0x0C, 0xB3, 0x92, 0xED, 0xE7, 0xBF, 0x2F, 0x4D, 0x13, 0xF7, 0xCF, 0x9A, + 0xBF, 0xCC, 0x44, 0x02, 0xD9, 0x64, 0x04, 0xB9, 0xC6, 0x49, 0x22, 0x41, 0x04, 0x35, 0x9A, 0xE6, + 0x1C, 0x84, 0x5B, 0x03, 0xD8, 0xDE, 0x6D, 0xFA, 0x74, 0x6C, 0xCE, 0xE7, 0x7B, 0x0D, 0x99, 0xD7, + 0xA0, 0x6C, 0xF1, 0x12, 0x16, 0x8B, 0xFD, 0x51, 0xC6, 0x3D, 0xE4, 0x41, 0x1B, 0x53, 0x83, 0x9A, + 0xB3, 0x84, 0x8A, 0x2C, 0xE1, 0x9A, 0x1F, 0x79, 0x19, 0x1A, 0xBB, 0x3D, 0xA6, 0xE2, 0x58, 0xD9, + 0x7D, 0xF7, 0xE1, 0x8D, 0x0F, 0x3B, 0xE6, 0x0B, 0x04, 0x6F, 0x2D, 0x02, 0x38, 0x30, 0x9C, 0x97, + 0xE3, 0x54, 0xF6, 0x43, 0x82, 0x01, 0x22, 0xEF, 0xE8, 0x83, 0x41, 0x2D, 0xB1, 0x40, 0xA4, 0x36, + 0xAE, 0x1B, 0xC5, 0x2E, 0x80, 0x71, 0x73, 0x76, 0x07, 0x4A, 0x20, 0x2E, 0xFD, 0x22, 0x6E, 0x2C, + 0xE6, 0x72, 0xF8, 0x69, 0xE7, 0xBB, 0xC9, 0x1E, 0x3B, 0xA8, 0xB7, 0x1C, 0xB2, 0xCF, 0x0E, 0x5A, + 0xE0, 0x5E, 0x65, 0x6E, 0xE4, 0xB9, 0xAF, 0x58, 0x40, 0x07, 0xB9, 0xC3, 0xE1, 0x31, 0x48, 0x6C, + 0xB1, 0x85, 0x28, 0xE2, 0x5B, 0xCD, 0xE6, 0x86, 0x4B, 0x0F, 0x48, 0x00, 0x39, 0xCC, 0xD0, 0x8F, + 0xAF, 0xAE, 0x2E, 0xAE, 0xBE, 0xE8, 0x35, 0x5A, 0xD3, 0x6F, 0x1C, 0x4D, 0xAF, 0x71, 0xD3, 0x11, + 0x76, 0x42, 0x47, 0x09, 0x4D, 0x27, 0x97, 0x44, 0x4C, 0x8C, 0xD4, 0xBE, 0x23, 0x41, 0x56, 0x16, + 0x84, 0xA1, 0xDC, 0xC8, 0xA2, 0x70, 0x39, 0x9D, 0x6A, 0xAF, 0x40, 0xCD, 0x47, 0x90, 0xEA, 0xDA, + 0xC2, 0x26, 0x71, 0x4C, 0xB9, 0x6F, 0xE8, 0x31, 0x20, 0xEA, 0x16, 0x35, 0xAD, 0x84, 0x7E, 0xCB, + 0x68, 0x2A, 0x52, 0x1B, 0x2C, 0xD7, 0xD0, 0x2F, 0x07, 0x7D, 0xDD, 0xD2, 0x1B, 0xE8, 0x47, 0x3A, + 0xF0, 0x46, 0xCC, 0x39, 0x52, 0x89, 0x5C, 0xD0, 0xA4, 0x3E, 0xCC, 0xC0, 0xA0, 0xB8, 0x6E, 0xB6, + 0x23, 0x9B, 0x71, 0x4E, 0x93, 0x93, 0xFE, 0xD9, 0xA9, 0xAB, 0x5F, 0x29, 0x46, 0xB4, 0x53, 0x28, + 0x48, 0x74, 0x4B, 0x5E, 0x51, 0x7E, 0xC8, 0xE1, 0x84, 0x05, 0xBE, 0x11, 0x99, 0x6D, 0x24, 0xE1, + 0x49, 0x12, 0xB2, 0x40, 0x01, 0x0A, 0x9E, 0x2D, 0x1E, 0x62, 0xEA, 0xEA, 0x23, 0x50, 0x86, 0x6E, + 0x79, 0x76, 0x98, 0x05, 0x82, 0xC5, 0x01, 0x75, 0x37, 0x5A, 0x30, 0xE3, 0x60, 0x41, 0xAE, 0x8E, + 0xB9, 0x19, 0x61, 0xCC, 0x77, 0x75, 0x15, 0xA1, 0xF2, 0xB8, 0xB6, 0xEE, 0x14, 0x4F, 0x9D, 0x92, + 0x56, 0x4E, 0x49, 0xCB, 0xB8, 0x4A, 0xE0, 0x34, 0x3F, 0x18, 0xC3, 0x3C, 0xCE, 0xD4, 0x51, 0x05, + 0xCC, 0xA7, 0x23, 0x02, 0x9C, 0x7C, 0x40, 0x6D, 0xBA, 0x7A, 0x63, 0xDD, 0x41, 0xA9, 0x3A, 0xC8, + 0xAF, 0x6A, 0xC4, 0x2F, 0x6B, 0x44, 0xDD, 0xEE, 0x3A, 0x64, 0x5F, 0x21, 0x07, 0x55, 0xE4, 0xA0, + 0x8C, 0x7C, 0x28, 0x8D, 0x64, 0x1D, 0x72, 0xA0, 0x90, 0x93, 0x8A, 0x88, 0x89, 0x14, 0x51, 0x85, + 0xBD, 0x3A, 0x6A, 0x13, 0x05, 0xD2, 0xAD, 0xA4, 0x22, 0x66, 0x62, 0x83, 0x97, 0x92, 0x61, 0x40, + 0x7D, 0x77, 0xA3, 0x09, 0x33, 0x2C, 0xB6, 0xDD, 0xAD, 0xE6, 0x9A, 0x33, 0x12, 0x75, 0x46, 0x56, + 0x65, 0x30, 0x2B, 0x33, 0xA8, 0xF5, 0xC8, 0x1D, 0xD5, 0xD6, 0x31, 0x98, 0x99, 0x56, 0x60, 0x47, + 0xDC, 0x0B, 0x98, 0x77, 0xEB, 0x2E, 0xBD, 0xC5, 0x9C, 0xB1, 0x85, 0x85, 0x5A, 0x5C, 0x06, 0xBA, + 0x01, 0x94, 0x5E, 0x8B, 0xA5, 0x7C, 0x80, 0xFA, 0x9E, 0x5B, 0xD9, 0x5A, 0x02, 0xDC, 0xA6, 0xF7, + 0xD4, 0x3B, 0x8C, 0xC2, 0x90, 0xA0, 0xED, 0xA6, 0xC0, 0x41, 0x3E, 0xD1, 0xCD, 0xB9, 0x15, 0xAD, + 0xC5, 0x79, 0xC2, 0x45, 0x2C, 0x7F, 0x3D, 0x8B, 0x23, 0x03, 0x5C, 0xCE, 0xF5, 0x6C, 0xD4, 0x61, + 0x6A, 0x83, 0x1E, 0xC7, 0x62, 0xF2, 0x13, 0x17, 0x2A, 0x0C, 0x54, 0xA2, 0x7C, 0x69, 0xDE, 0x58, + 0x0B, 0x91, 0x56, 0x7C, 0xEA, 0xA2, 0xB7, 0xE2, 0x54, 0xA8, 0xBC, 0x8A, 0x5D, 0x9A, 0x4B, 0x1D, + 0x94, 0x61, 0xB9, 0xBD, 0x2F, 0xA0, 0xFA, 0x7C, 0x0E, 0xE7, 0x01, 0xFF, 0x13, 0x68, 0xF9, 0xE8, + 0x5F, 0x17, 0x60, 0xC9, 0xA3, 0x34, 0x78, 0x8B, 0xBB, 0x0D, 0xE3, 0xC0, 0xF9, 0x8F, 0x6D, 0x7C, + 0xF9, 0x1F, 0xFB, 0xA6, 0x66, 0x9A, 0x07, 0xFF, 0x6A, 0x48, 0x0D, 0x1B, 0xC2, 0xFC, 0xD2, 0xBA, + 0xB1, 0x08, 0x80, 0xED, 0x7F, 0x9B, 0xFF, 0xB1, 0x25, 0xB8, 0x02, 0x6B, 0xDF, 0x45, 0x90, 0x49, + 0xF0, 0x24, 0x34, 0xB0, 0x68, 0xA4, 0x91, 0xCD, 0x4D, 0x43, 0xB8, 0xA4, 0x72, 0x8D, 0x35, 0x51, + 0xD3, 0x6D, 0x88, 0x53, 0x50, 0x5B, 0xAC, 0x04, 0xBF, 0x3E, 0x24, 0x7A, 0x15, 0x5B, 0x17, 0x00, + 0xC9, 0x3D, 0xCA, 0x0C, 0x3D, 0x22, 0x97, 0x52, 0xCB, 0x0C, 0x02, 0x42, 0xA7, 0x89, 0xE7, 0x2A, + 0xAD, 0x1D, 0x14, 0x30, 0x17, 0xA2, 0xE0, 0xBC, 0x1C, 0x2D, 0x15, 0xEA, 0xAA, 0xFD, 0x17, 0x0A, + 0xA3, 0xD6, 0x12, 0x8A, 0x04, 0x31, 0xAD, 0xD8, 0x79, 0xC6, 0x72, 0x75, 0x4C, 0x59, 0xBA, 0x35, + 0x59, 0x5D, 0x96, 0xAD, 0x04, 0xAE, 0x2F, 0x8D, 0xFE, 0xD7, 0x3D, 0x16, 0x8E, 0xB5, 0x12, 0x3F, + 0xF8, 0x97, 0xFB, 0x2B, 0x46, 0xE4, 0xCD, 0x3F, 0xBC, 0x21, 0x70, 0x05, 0xA6, 0x41, 0x6D, 0x1E, + 0x4D, 0x0D, 0xB3, 0xF6, 0xAB, 0xAE, 0x49, 0x8A, 0xAE, 0x1E, 0x92, 0xFB, 0xBC, 0xA7, 0xC4, 0x8C, + 0xD7, 0xD6, 0x70, 0x5E, 0xB4, 0x28, 0xF9, 0x82, 0xEC, 0xE6, 0x48, 0x26, 0xA2, 0xB6, 0x56, 0x64, + 0x52, 0xD5, 0xCA, 0xE8, 0x5A, 0x63, 0xFF, 0xD7, 0x4A, 0x40, 0xB7, 0x98, 0xBA, 0x4E, 0x15, 0x8C, + 0xB3, 0x00, 0x1C, 0x93, 0x3E, 0x1D, 0x69, 0x03, 0x26, 0x03, 0x75, 0x35, 0x46, 0x5A, 0x81, 0xC1, + 0xCC, 0x03, 0xC3, 0x2B, 0xFB, 0xF3, 0x1E, 0x16, 0xBF, 0xFB, 0x97, 0xAA, 0xAA, 0x81, 0xD4, 0x8B, + 0x33, 0x5D, 0x59, 0x59, 0xD5, 0x4B, 0xE0, 0xD2, 0x08, 0xA0, 0x5B, 0x8B, 0x3C, 0x3A, 0x8C, 0xFC, + 0x87, 0x52, 0xF6, 0x4D, 0xBB, 0x0F, 0x87, 0x01, 0x49, 0xD3, 0x73, 0xB8, 0x01, 0x43, 0xF7, 0x42, + 0x50, 0xB8, 0xB2, 0xC2, 0xFD, 0xE6, 0xE6, 0x66, 0x15, 0x29, 0xA1, 0x21, 0x14, 0xDB, 0x8A, 0x2B, + 0xF0, 0x49, 0xD3, 0xF1, 0x81, 0x30, 0x18, 0xD2, 0x1A, 0xC6, 0xF0, 0x25, 0xE3, 0x47, 0x5C, 0x71, + 0xF4, 0xF4, 0x22, 0xA6, 0xFC, 0x33, 0xDC, 0x95, 0x32, 0xCB, 0x1A, 0xAD, 0xA6, 0x68, 0xFA, 0x8F, + 0xD8, 0x3E, 0xCA, 0x0D, 0x76, 0xC1, 0x7A, 0xBA, 0x56, 0xA1, 0xFC, 0x9F, 0x61, 0xB9, 0x94, 0x28, + 0xD6, 0x70, 0x9C, 0x40, 0x80, 0x5A, 0xC3, 0x31, 0xC4, 0x1A, 0x41, 0x17, 0xFC, 0x26, 0x6B, 0xF9, + 0xCD, 0xFE, 0x19, 0x7E, 0x97, 0x76, 0x1E, 0x15, 0x25, 0x91, 0xAA, 0xAF, 0x50, 0x02, 0x9F, 0xDD, + 0xE9, 0xA6, 0x15, 0xB9, 0x55, 0x0A, 0x50, 0x1B, 0x46, 0x41, 0xD0, 0x8F, 0xE2, 0x83, 0x27, 0xD6, + 0x9D, 0xC5, 0x7A, 0x31, 0xC8, 0xD9, 0x5C, 0x6E, 0xB1, 0xBC, 0xB5, 0x44, 0x4F, 0xA1, 0xEC, 0x5F, + 0x4B, 0x15, 0x01, 0x3F, 0x23, 0x8B, 0x7B, 0xAC, 0xD4, 0xA5, 0x36, 0x28, 0x0F, 0x56, 0x3F, 0xD5, + 0x3C, 0xCB, 0x5F, 0xCC, 0xAE, 0x6B, 0x51, 0x9B, 0xC0, 0x38, 0x57, 0x92, 0x8B, 0x4A, 0xB2, 0xC8, + 0x13, 0x01, 0xA8, 0x58, 0xC7, 0x2E, 0xC4, 0x4D, 0x6B, 0x7A, 0x7C, 0xBF, 0x5C, 0x83, 0xC2, 0xDF, + 0xF5, 0xD5, 0x12, 0x33, 0x08, 0xC4, 0xD3, 0x95, 0x4B, 0x29, 0x5F, 0x37, 0x29, 0x8A, 0x0E, 0x62, + 0x47, 0xA3, 0x51, 0x4A, 0xC5, 0x47, 0x0C, 0x49, 0x56, 0xB2, 0x98, 0x9F, 0xC8, 0x90, 0x04, 0x8C, + 0x45, 0x3C, 0x8C, 0xB2, 0x94, 0x46, 0x99, 0xA8, 0xA4, 0x16, 0x63, 0x21, 0xCC, 0x5E, 0xFA, 0xE7, + 0x9F, 0x8B, 0xC9, 0x7E, 0x5A, 0x0B, 0x96, 0xD3, 0xEB, 0x3D, 0xBF, 0x34, 0xD9, 0xF7, 0x6B, 0x89, + 0xB9, 0x7A, 0xE9, 0xFF, 0x67, 0x4B, 0x21, 0x65, 0x4B, 0xF1, 0xB0, 0x54, 0x2E, 0x62, 0x62, 0x29, + 0xE6, 0xC9, 0x82, 0x91, 0x97, 0x7C, 0x16, 0x0D, 0x1A, 0x2B, 0x25, 0x55, 0x9E, 0x97, 0x7D, 0x95, + 0x43, 0x40, 0x59, 0x71, 0xE5, 0x35, 0x11, 0x06, 0x34, 0xE0, 0x63, 0x64, 0xF2, 0x41, 0xEB, 0xA7, + 0xD1, 0x94, 0x26, 0x87, 0x24, 0xA5, 0x06, 0x24, 0xCD, 0x65, 0xDC, 0x41, 0xA8, 0xE9, 0x04, 0xEB, + 0x76, 0x6D, 0x6E, 0x12, 0x05, 0xCE, 0x33, 0x77, 0xC4, 0xB1, 0x26, 0x03, 0xF9, 0xB2, 0xCA, 0x09, + 0xD4, 0xC6, 0xBE, 0x12, 0xA4, 0x3E, 0x52, 0x25, 0xA8, 0x61, 0x5A, 0xD0, 0x76, 0xC0, 0x35, 0x5F, + 0x26, 0x51, 0x4C, 0xC6, 0xB2, 0x07, 0x83, 0x35, 0x74, 0x0F, 0xA4, 0x66, 0x6D, 0x34, 0x91, 0x60, + 0xA9, 0x73, 0x29, 0xFC, 0x66, 0xD9, 0xC2, 0x70, 0x4B, 0x57, 0xC9, 0xB0, 0xBD, 0xF4, 0xA5, 0x35, + 0x59, 0x83, 0xE0, 0x0B, 0x6C, 0x62, 0xE0, 0x1E, 0x68, 0x64, 0xF2, 0x7B, 0x00, 0x77, 0x6B, 0xB6, + 0xA3, 0x3D, 0xD6, 0x8E, 0x6A, 0x35, 0x53, 0x55, 0xE9, 0xAE, 0x0B, 0x6D, 0x4E, 0x74, 0x23, 0x0B, + 0x4B, 0x10, 0xAA, 0x9A, 0x59, 0x0C, 0x38, 0x1B, 0x81, 0xAA, 0xBA, 0xC0, 0x11, 0xD6, 0x98, 0x66, + 0xA9, 0x23, 0xF1, 0x97, 0x1D, 0xC9, 0x13, 0xB5, 0x07, 0x95, 0xF5, 0x05, 0xD4, 0x31, 0xAB, 0x25, + 0x86, 0x30, 0xD3, 0x29, 0x13, 0xDE, 0x04, 0x03, 0x90, 0x07, 0x5A, 0xD5, 0x05, 0x14, 0xB5, 0x8E, + 0x1C, 0x4D, 0x44, 0xB8, 0x1C, 0x05, 0xF9, 0xF0, 0x6B, 0x9A, 0x0F, 0xBC, 0xB4, 0x18, 0xDD, 0x97, + 0x80, 0x50, 0xD2, 0xE6, 0xE0, 0x88, 0x8F, 0xF2, 0x21, 0xF4, 0xB2, 0x05, 0x9D, 0x02, 0x58, 0xFC, + 0xC6, 0x71, 0x3E, 0x8A, 0x27, 0xC5, 0x68, 0x42, 0xEF, 0x17, 0x78, 0x51, 0x01, 0xF5, 0xA9, 0xEE, + 0x28, 0x1B, 0xDB, 0x68, 0xCE, 0xF3, 0x41, 0x6B, 0x29, 0x7F, 0xF0, 0xFF, 0x28, 0x7F, 0xCC, 0xC7, + 0x85, 0x34, 0x71, 0x31, 0x1A, 0xB3, 0x42, 0x96, 0x61, 0x18, 0xFF, 0x90, 0x93, 0xA4, 0xD4, 0x13, + 0x97, 0x7A, 0x5A, 0xF1, 0xB3, 0xB6, 0x53, 0x98, 0x8E, 0x31, 0xAA, 0xF8, 0xE3, 0xC8, 0xF6, 0xF0, + 0xF7, 0x3C, 0xF2, 0x65, 0x6D, 0x69, 0x5A, 0xA1, 0x31, 0x82, 0x3A, 0x57, 0x37, 0xCB, 0x7E, 0x9A, + 0xFD, 0xB7, 0xAD, 0xE8, 0xD1, 0xF1, 0xE9, 0x71, 0xFF, 0xB8, 0x5C, 0x38, 0x23, 0xE7, 0x25, 0x93, + 0x8A, 0x2B, 0x5D, 0xFA, 0xB2, 0x22, 0x80, 0x02, 0x1B, 0x45, 0x01, 0x7B, 0xDD, 0xDC, 0x54, 0x7E, + 0xF1, 0xB6, 0x77, 0x71, 0x6E, 0xC7, 0x24, 0x01, 0x8F, 0x24, 0x15, 0xE6, 0xC2, 0x82, 0x44, 0xF9, + 0xE0, 0xD7, 0xC7, 0xA5, 0x72, 0x5D, 0x7E, 0x61, 0x70, 0xC4, 0xDC, 0x52, 0xA7, 0xA9, 0x7E, 0x78, + 0xE2, 0x62, 0x5D, 0x99, 0xBF, 0x04, 0x41, 0x72, 0x1A, 0x2D, 0x13, 0x55, 0x11, 0x67, 0x46, 0xE5, + 0x30, 0x2F, 0xEE, 0xB2, 0x75, 0x0D, 0xD3, 0xC8, 0xB4, 0xC4, 0x84, 0xA5, 0xE5, 0x46, 0xA5, 0x12, + 0x14, 0xFE, 0xA2, 0xB6, 0xE7, 0x8B, 0x91, 0x24, 0xB7, 0x5A, 0x73, 0xAB, 0x6F, 0x41, 0x2A, 0x3E, + 0x58, 0x04, 0x23, 0x66, 0x39, 0xDB, 0x16, 0x77, 0xA3, 0x43, 0xEE, 0x61, 0x5C, 0x7F, 0xBA, 0x35, + 0x78, 0xD2, 0x3C, 0x79, 0x61, 0x9E, 0xFC, 0xB1, 0x7B, 0x2E, 0x1C, 0x45, 0xF9, 0xDA, 0xE2, 0x98, + 0xF6, 0x10, 0x58, 0xBB, 0x6D, 0x2F, 0x7D, 0x18, 0x20, 0xD2, 0x83, 0xCB, 0x00, 0xF4, 0x63, 0x58, + 0xFF, 0x4A, 0xEE, 0x88, 0x7A, 0x09, 0xAA, 0xA2, 0xAD, 0x73, 0x54, 0xD8, 0xEE, 0xFD, 0x81, 0xA3, + 0xF2, 0xCE, 0x65, 0x18, 0x48, 0x97, 0xC3, 0x92, 0x37, 0x8B, 0x75, 0xC1, 0x61, 0x19, 0x31, 0x64, + 0x6C, 0x00, 0xE3, 0xCD, 0x5D, 0x49, 0x13, 0xD5, 0x1C, 0xB4, 0xF0, 0x1B, 0x08, 0x8A, 0x4F, 0x39, + 0xCE, 0x9A, 0x38, 0xAD, 0x62, 0x72, 0xC5, 0x23, 0xC8, 0x4A, 0x67, 0x89, 0xC0, 0x6E, 0x10, 0x0D, + 0x0D, 0x7C, 0x64, 0x9A, 0xA1, 0xB6, 0x1D, 0x3E, 0x37, 0xD7, 0xBC, 0xD9, 0x54, 0xFA, 0x4B, 0x62, + 0x79, 0xD5, 0xB0, 0x8B, 0x1C, 0x56, 0xCC, 0x75, 0x7D, 0x1F, 0xF4, 0xA3, 0x4E, 0x29, 0xAF, 0x48, + 0xA4, 0x53, 0xD1, 0x83, 0xC4, 0x86, 0xA2, 0x41, 0xBE, 0x91, 0x40, 0x44, 0x72, 0x4A, 0x33, 0x5D, + 0xC7, 0xCA, 0xD2, 0x0B, 0x28, 0x49, 0x7A, 0xB2, 0x73, 0x95, 0x49, 0x6B, 0x25, 0x06, 0xFE, 0xC8, + 0xD7, 0xF0, 0xC7, 0xA1, 0xD0, 0xA3, 0x83, 0x9B, 0x49, 0x2B, 0x83, 0xA4, 0x23, 0x64, 0x83, 0xA9, + 0x37, 0xE4, 0xBB, 0xA8, 0x2D, 0x2F, 0xCB, 0xB4, 0x16, 0x50, 0x70, 0x71, 0x83, 0xBB, 0x11, 0x30, + 0x52, 0x5A, 0xC4, 0x9E, 0x94, 0xA8, 0xC7, 0x8F, 0x10, 0x1F, 0x53, 0x4A, 0x20, 0x06, 0x20, 0xA6, + 0x40, 0xD0, 0xA7, 0x42, 0x8A, 0x54, 0xE6, 0x92, 0x53, 0x2A, 0x20, 0xCA, 0x48, 0xCD, 0xE2, 0xC1, + 0x85, 0x78, 0xD4, 0x46, 0xD6, 0x80, 0xFD, 0xDC, 0xBD, 0x73, 0x33, 0xDE, 0x90, 0x68, 0x09, 0x56, + 0x36, 0x3D, 0x9A, 0xA6, 0x52, 0x5C, 0x54, 0xC7, 0x19, 0xF8, 0xA8, 0xA1, 0x03, 0x5A, 0x23, 0x84, + 0x11, 0x1E, 0x84, 0x8A, 0x01, 0x40, 0x7F, 0x42, 0xC3, 0x1C, 0x22, 0x70, 0x08, 0x20, 0x82, 0xA0, + 0x7F, 0x49, 0x0D, 0xF7, 0x64, 0x05, 0xC9, 0xF8, 0xD8, 0x6D, 0x35, 0xF0, 0x9D, 0x66, 0x95, 0xEC, + 0x20, 0xA5, 0xBD, 0x68, 0x24, 0xFA, 0x64, 0x98, 0x1A, 0x50, 0x00, 0xAC, 0xD9, 0x01, 0xA0, 0x1E, + 0x24, 0x5E, 0x63, 0x2B, 0x3F, 0xEF, 0x04, 0x2A, 0xBB, 0x00, 0xAB, 0xBB, 0x8E, 0x87, 0x5F, 0x39, + 0x4F, 0x19, 0xA7, 0x39, 0x26, 0x00, 0x7B, 0x93, 0x68, 0x7A, 0x99, 0x30, 0x2E, 0xCE, 0x64, 0x1B, + 0x6A, 0x6C, 0xB4, 0xE4, 0xF5, 0xA9, 0x87, 0x15, 0x79, 0x3F, 0xC5, 0x8B, 0xCB, 0x0C, 0xF3, 0xBA, + 0x53, 0x79, 0x77, 0xB1, 0x86, 0x70, 0x21, 0x50, 0x66, 0x38, 0xB3, 0x29, 0x74, 0xB0, 0xFA, 0xA1, + 0x48, 0x82, 0x7A, 0x4F, 0xB7, 0x42, 0xE2, 0xC1, 0x44, 0xED, 0x81, 0xF9, 0xDC, 0xC2, 0xD8, 0xE1, + 0x94, 0x83, 0x5A, 0x0A, 0xB5, 0x02, 0x45, 0xC6, 0x95, 0xCD, 0x98, 0x35, 0x1D, 0x6A, 0x58, 0x88, + 0x61, 0xE0, 0xAF, 0xFE, 0x05, 0x0F, 0x1E, 0x1C, 0xC8, 0x55, 0x3F, 0xE1, 0x23, 0xE3, 0x7E, 0xF4, + 0x23, 0x3E, 0x3E, 0xAF, 0xF0, 0xF1, 0x79, 0x1D, 0x1F, 0xB4, 0xAA, 0x3C, 0x98, 0x0C, 0x80, 0xEC, + 0x19, 0xE1, 0x64, 0x4C, 0x13, 0x58, 0xC0, 0x43, 0x50, 0x25, 0x7F, 0x8B, 0xB3, 0x84, 0xFE, 0x98, + 0xB3, 0xDE, 0x84, 0x8D, 0xC4, 0x23, 0xFE, 0x8A, 0xD5, 0xFF, 0x82, 0x4B, 0x3C, 0x70, 0x3D, 0x97, + 0x79, 0x6D, 0x5A, 0x49, 0x28, 0x3F, 0x7E, 0x2B, 0x91, 0x7E, 0xE4, 0x42, 0x78, 0xA9, 0x38, 0xC8, + 0xDF, 0xB7, 0xF4, 0x00, 0xBC, 0x11, 0xF8, 0x29, 0x35, 0x75, 0xBC, 0x0B, 0xA5, 0xFC, 0x29, 0x30, + 0x64, 0xA8, 0xC0, 0x47, 0xDD, 0xD9, 0xDC, 0x12, 0xAE, 0x01, 0x8A, 0xF1, 0xA3, 0x29, 0xB0, 0xEA, + 0xC9, 0x02, 0xD7, 0x9E, 0x40, 0x26, 0x04, 0x91, 0xE0, 0x48, 0xC8, 0xA7, 0x8D, 0x2F, 0x07, 0x9B, + 0x37, 0x35, 0xC8, 0x43, 0x2E, 0xFC, 0x98, 0x2E, 0x0C, 0x36, 0x6F, 0xFE, 0x6D, 0x36, 0xC6, 0xCC, + 0x5A, 0x76, 0xA4, 0x96, 0x4C, 0xF6, 0xF4, 0x0B, 0xBF, 0x71, 0x09, 0x48, 0x5D, 0x49, 0x78, 0x45, + 0x34, 0x03, 0x6B, 0x43, 0x61, 0xE1, 0x07, 0xFF, 0x47, 0x09, 0xF8, 0x91, 0x9E, 0x07, 0xCE, 0xBD, + 0xE6, 0x3D, 0x5E, 0x2F, 0x3E, 0x85, 0xE9, 0x56, 0xE9, 0xC1, 0x4A, 0xC7, 0xEF, 0x53, 0x3A, 0x76, + 0x59, 0xA2, 0x14, 0x4A, 0x14, 0x59, 0x88, 0x1A, 0x6A, 0x50, 0x0E, 0x51, 0x98, 0x89, 0x17, 0xCD, + 0x81, 0x02, 0x9B, 0x73, 0x34, 0x5B, 0x3A, 0x02, 0x0F, 0xF4, 0xF5, 0x45, 0xEE, 0xFC, 0x74, 0x76, + 0x7A, 0x22, 0x44, 0x7C, 0xA5, 0x62, 0x22, 0xD0, 0xAA, 0x2E, 0x2C, 0x2F, 0xCF, 0x9C, 0x89, 0xE4, + 0xA1, 0x28, 0x75, 0x30, 0x31, 0x28, 0x87, 0xFE, 0x74, 0x31, 0xFC, 0x0A, 0x71, 0xD6, 0xD0, 0xCF, + 0x52, 0x48, 0x58, 0x5B, 0x36, 0xA2, 0xF7, 0xFB, 0x97, 0xF6, 0xAE, 0xDD, 0x84, 0xBA, 0x00, 0xB4, + 0x0A, 0x69, 0x19, 0xEE, 0x7D, 0xFE, 0xB7, 0x90, 0xB7, 0xFF, 0x1E, 0x32, 0x83, 0xA8, 0x95, 0x42, + 0x58, 0x2A, 0xF0, 0xAB, 0xB8, 0x93, 0x24, 0x9A, 0x4A, 0xB4, 0xE3, 0x24, 0xC1, 0x4B, 0xE9, 0x43, + 0x85, 0xA2, 0x0D, 0x61, 0x31, 0xA5, 0x89, 0xE6, 0x47, 0x34, 0xD5, 0x78, 0x24, 0xB4, 0x34, 0x8B, + 0x63, 0x68, 0x5C, 0x56, 0xF4, 0x61, 0xEB, 0xC5, 0xEB, 0xCB, 0xFB, 0x8C, 0x66, 0xD4, 0xCF, 0x97, + 0x69, 0x52, 0xD1, 0x0B, 0x56, 0x50, 0xDF, 0x10, 0xEE, 0x7E, 0xB9, 0xC9, 0xEB, 0xA9, 0x8C, 0x73, + 0x8C, 0xA2, 0x1B, 0x2D, 0x35, 0x07, 0xE9, 0x26, 0x40, 0xD5, 0xE5, 0x59, 0x10, 0xCC, 0xDB, 0x2B, + 0xB4, 0xA0, 0xF1, 0x8A, 0x44, 0x24, 0x9F, 0xCB, 0x67, 0x7F, 0xE4, 0xC9, 0xA9, 0xE2, 0x82, 0x50, + 0xF2, 0x54, 0xA9, 0x36, 0xAD, 0x0D, 0x63, 0x83, 0x6A, 0x8C, 0xA7, 0x82, 0x70, 0x0F, 0xAF, 0x51, + 0xE9, 0xC2, 0x2C, 0x6A, 0x29, 0xDC, 0xDE, 0x46, 0x5F, 0xCB, 0x6D, 0xE9, 0x89, 0x7C, 0x2A, 0x25, + 0xE3, 0xAE, 0xAE, 0x63, 0x55, 0x45, 0xB1, 0x3E, 0x25, 0x61, 0x5A, 0x26, 0x5B, 0x54, 0x06, 0x26, + 0x77, 0x0B, 0x70, 0x9B, 0x06, 0x29, 0x1C, 0xBD, 0x7E, 0x7F, 0xCE, 0x46, 0xD1, 0xCE, 0x11, 0x80, + 0x69, 0xC5, 0x3E, 0x93, 0xD7, 0xE0, 0x24, 0xCC, 0x73, 0x07, 0x32, 0xE9, 0x4A, 0x03, 0x0E, 0xA9, + 0x98, 0x44, 0xFE, 0x81, 0x7E, 0xA0, 0x3B, 0x3A, 0xFC, 0xBB, 0x09, 0x35, 0x47, 0xCD, 0xA5, 0xD0, + 0xA4, 0xFA, 0x74, 0x70, 0xF5, 0x06, 0xC2, 0x53, 0x0C, 0xA5, 0x01, 0x17, 0x50, 0x34, 0xD7, 0x74, + 0x7C, 0x7A, 0x7D, 0x0C, 0x29, 0xC8, 0x7F, 0x21, 0x37, 0x66, 0xBB, 0xAA, 0x6C, 0xB8, 0xF3, 0xEA, + 0x75, 0x56, 0x2E, 0x03, 0x7A, 0x61, 0x8C, 0x58, 0x0F, 0x29, 0x7E, 0xFB, 0x7B, 0xF4, 0x9E, 0x8D, + 0x15, 0xD2, 0x6A, 0x5D, 0x6F, 0xCE, 0x76, 0x90, 0x67, 0x89, 0xD5, 0x43, 0x2C, 0x70, 0x97, 0x1F, + 0x29, 0x59, 0x95, 0x35, 0xDC, 0xF6, 0x48, 0x10, 0xE0, 0xC7, 0x5A, 0x03, 0x1B, 0x6A, 0x22, 0xB2, + 0xD4, 0x42, 0x22, 0x29, 0x08, 0x90, 0xD2, 0x3E, 0x84, 0x39, 0xD3, 0x92, 0x65, 0x86, 0xB2, 0xA1, + 0xBC, 0xFF, 0xC5, 0x9A, 0xA3, 0x64, 0x46, 0xE8, 0xCE, 0xF9, 0x6C, 0x73, 0x53, 0xD8, 0x85, 0x99, + 0x18, 0x05, 0x52, 0x8A, 0x01, 0x1C, 0x9A, 0x7D, 0x68, 0x2D, 0x8C, 0xB2, 0x90, 0x58, 0xAB, 0x3D, + 0xD2, 0xB6, 0x51, 0x55, 0x03, 0x54, 0x7C, 0x46, 0x01, 0x03, 0xCE, 0xB2, 0x24, 0x80, 0xA8, 0x8B, + 0x39, 0xBA, 0xB2, 0x2D, 0xC5, 0xBA, 0xD0, 0x84, 0x0E, 0xEC, 0x67, 0xC8, 0x12, 0x95, 0x97, 0xAD, + 0xA2, 0x27, 0x12, 0xC5, 0x77, 0x95, 0x9E, 0xC8, 0x6F, 0xE5, 0x84, 0xAA, 0xC8, 0x77, 0x88, 0x2F, + 0x13, 0x5C, 0xD4, 0xD1, 0x13, 0xA0, 0x24, 0x83, 0x52, 0x34, 0x60, 0x2A, 0x2C, 0x37, 0xEE, 0xEB, + 0xD3, 0xE9, 0xB4, 0x8E, 0xDF, 0x6A, 0xEB, 0x70, 0x82, 0xB2, 0x02, 0x5F, 0x5F, 0xC7, 0x21, 0x47, + 0x15, 0x58, 0xF8, 0x6E, 0xE1, 0xAC, 0xBA, 0xE8, 0x42, 0x7F, 0x2B, 0xDE, 0xD4, 0xAA, 0xD2, 0x59, + 0xE1, 0x73, 0x79, 0xDB, 0x7B, 0x3B, 0x2B, 0x20, 0x32, 0xC4, 0xAF, 0xB2, 0x90, 0x69, 0x20, 0x0D, + 0x3B, 0xE5, 0x46, 0x56, 0x25, 0x85, 0x65, 0x5C, 0xB0, 0xE3, 0x2C, 0x9D, 0x18, 0x33, 0x60, 0xDD, + 0x11, 0x96, 0xD2, 0x95, 0x43, 0x2D, 0x65, 0xB7, 0x0E, 0xB7, 0x0A, 0xFB, 0x70, 0x30, 0x83, 0x94, + 0x79, 0xFB, 0xF3, 0x4F, 0x39, 0x5B, 0xDE, 0xF6, 0x92, 0x62, 0x71, 0xE1, 0xF3, 0xFC, 0xA9, 0x35, + 0xAF, 0x69, 0xA5, 0xD1, 0xAF, 0xC4, 0x97, 0xBD, 0x46, 0xFE, 0x19, 0x3B, 0xFF, 0x9C, 0xAD, 0x81, + 0xB1, 0x43, 0x23, 0x2A, 0xDC, 0x4C, 0x8C, 0xEA, 0x2F, 0x34, 0xE6, 0x63, 0x79, 0x29, 0xBF, 0x2D, + 0xA0, 0x54, 0xA9, 0xD3, 0x68, 0x78, 0x3E, 0xFF, 0x9A, 0x42, 0x19, 0x1D, 0x65, 0xFE, 0x28, 0x20, + 0x09, 0xC5, 0x82, 0xA3, 0x41, 0xBE, 0x92, 0xFB, 0x46, 0xC0, 0x86, 0x69, 0x03, 0x93, 0x6D, 0xCB, + 0xDE, 0xB2, 0x77, 0x71, 0x64, 0x7F, 0x4D, 0xF7, 0x57, 0x4F, 0xD8, 0x5F, 0x34, 0x69, 0x58, 0x0B, + 0xE7, 0xB5, 0xAB, 0x8A, 0x4D, 0x6A, 0x83, 0xFB, 0xC4, 0xA7, 0x70, 0x3D, 0x6F, 0xB3, 0xCC, 0xB6, + 0x1A, 0xE4, 0x5F, 0x60, 0xD4, 0x31, 0xBA, 0x95, 0x2F, 0x92, 0xF4, 0x81, 0x7B, 0x18, 0x5B, 0x17, + 0x54, 0x26, 0x70, 0x49, 0xD5, 0x87, 0x34, 0xB9, 0xD3, 0x9C, 0x2F, 0x39, 0xC3, 0xB7, 0x3C, 0xA8, + 0x03, 0xE4, 0x37, 0x9C, 0x72, 0x39, 0xB0, 0xBF, 0x07, 0x5D, 0x33, 0x2A, 0x41, 0x79, 0xB1, 0x26, + 0x9B, 0xE6, 0x7C, 0x02, 0x82, 0x01, 0x70, 0xB1, 0xA3, 0x48, 0xCD, 0x2B, 0xCB, 0x98, 0x9B, 0x57, + 0x96, 0x54, 0xE2, 0x5F, 0x59, 0xCC, 0xDB, 0x9F, 0xFC, 0xDB, 0x4C, 0xF9, 0x7F, 0x5B, 0x28, 0x36, + 0x32, 0xF9, 0xE1, 0x09, 0xF7, 0x56, 0x3F, 0x45, 0xAD, 0x47, 0x51, 0xBB, 0xF7, 0xFF, 0x17, 0x53, + 0xE8, 0x9D, 0x36, 0x92, 0x29, 0x00, 0x00 +}; + +#define SPIFFS_MAXLENGTH_FILEPATH 32 +const char *excludeListFile = "/.exclude.files"; + +typedef struct ExcludeListS { + char *item; + ExcludeListS *next; +} ExcludeList; + +static ExcludeList *excludes = NULL; + +static bool matchWild(const char *pattern, const char *testee) { + const char *nxPat = NULL, *nxTst = NULL; + + while (*testee) { + if (( *pattern == '?' ) || (*pattern == *testee)){ + pattern++;testee++; + continue; + } + if (*pattern=='*'){ + nxPat=pattern++; nxTst=testee; + continue; + } + if (nxPat){ + pattern = nxPat+1; testee=++nxTst; + continue; + } + return false; + } + while (*pattern=='*'){pattern++;} + return (*pattern == 0); +} + +static bool addExclude(const char *item){ + size_t len = strlen(item); + if(!len){ + return false; + } + ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList)); + if(!e){ + return false; + } + e->item = (char *)malloc(len+1); + if(!e->item){ + free(e); + return false; + } + memcpy(e->item, item, len+1); + e->next = excludes; + excludes = e; + return true; +} + +static void loadExcludeList(fs::FS &_fs, const char *filename){ + static char linebuf[SPIFFS_MAXLENGTH_FILEPATH]; + fs::File excludeFile=_fs.open(filename, "r"); + if(!excludeFile){ + //addExclude("/*.js.gz"); + return; + } +#ifdef ESP32 + if(excludeFile.isDirectory()){ + excludeFile.close(); + return; + } +#endif + if (excludeFile.size() > 0){ + uint8_t idx; + bool isOverflowed = false; + while (excludeFile.available()){ + linebuf[0] = '\0'; + idx = 0; + int lastChar; + do { + lastChar = excludeFile.read(); + if(lastChar != '\r'){ + linebuf[idx++] = (char) lastChar; + } + } while ((lastChar >= 0) && (lastChar != '\n') && (idx < SPIFFS_MAXLENGTH_FILEPATH)); + + if(isOverflowed){ + isOverflowed = (lastChar != '\n'); + continue; + } + isOverflowed = (idx >= SPIFFS_MAXLENGTH_FILEPATH); + linebuf[idx-1] = '\0'; + if(!addExclude(linebuf)){ + excludeFile.close(); + return; + } + } + } + excludeFile.close(); +} + +static bool isExcluded(fs::FS &_fs, const char *filename) { + if(excludes == NULL){ + loadExcludeList(_fs, excludeListFile); + } + ExcludeList *e = excludes; + while(e){ + if (matchWild(e->item, filename)){ + return true; + } + e = e->next; + } + return false; +} + +// WEB HANDLER IMPLEMENTATION + +#ifdef ESP32 +SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password) +#else +SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs) +#endif +:_fs(fs) +,_username(username) +,_password(password) +,_authenticated(false) +,_startTime(0) +{} + +bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request){ + if(request->url().equalsIgnoreCase("/edit")){ + if(request->method() == HTTP_GET){ + if(request->hasParam("list")) + return true; + if(request->hasParam("edit")){ + request->_tempFile = _fs.open(request->arg("edit"), "r"); + if(!request->_tempFile){ + return false; + } +#ifdef ESP32 + if(request->_tempFile.isDirectory()){ + request->_tempFile.close(); + return false; + } +#endif + } + if(request->hasParam("download")){ + request->_tempFile = _fs.open(request->arg("download"), "r"); + if(!request->_tempFile){ + return false; + } +#ifdef ESP32 + if(request->_tempFile.isDirectory()){ + request->_tempFile.close(); + return false; + } +#endif + } + request->addInterestingHeader("If-Modified-Since"); + return true; + } + else if(request->method() == HTTP_POST) + return true; + else if(request->method() == HTTP_DELETE) + return true; + else if(request->method() == HTTP_PUT) + return true; + + } + return false; +} + + +void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request){ + if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + + if(request->method() == HTTP_GET){ + if(request->hasParam("list")){ + String path = request->getParam("list")->value(); +#ifdef ESP32 + File dir = _fs.open(path); +#else + Dir dir = _fs.openDir(path); +#endif + path = String(); + String output = "["; +#ifdef ESP32 + File entry = dir.openNextFile(); + while(entry){ +#else + while(dir.next()){ + fs::File entry = dir.openFile("r"); +#endif + if (isExcluded(_fs, entry.name())) { +#ifdef ESP32 + entry = dir.openNextFile(); +#endif + continue; + } + if (output != "[") output += ','; + output += "{\"type\":\""; + output += "file"; + output += "\",\"name\":\""; + output += String(entry.name()); + output += "\",\"size\":"; + output += String(entry.size()); + output += "}"; +#ifdef ESP32 + entry = dir.openNextFile(); +#else + entry.close(); +#endif + } +#ifdef ESP32 + dir.close(); +#endif + output += "]"; + request->send(200, "application/json", output); + output = String(); + } + else if(request->hasParam("edit") || request->hasParam("download")){ + request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download")); + } + else { + const char * buildTime = __DATE__ " " __TIME__ " GMT"; + if (request->header("If-Modified-Since").equals(buildTime)) { + request->send(304); + } else { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Last-Modified", buildTime); + request->send(response); + } + } + } else if(request->method() == HTTP_DELETE){ + if(request->hasParam("path", true)){ + _fs.remove(request->getParam("path", true)->value()); + request->send(200, "", "DELETE: "+request->getParam("path", true)->value()); + } else + request->send(404); + } else if(request->method() == HTTP_POST){ + if(request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value())) + request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value()); + else + request->send(500); + } else if(request->method() == HTTP_PUT){ + if(request->hasParam("path", true)){ + String filename = request->getParam("path", true)->value(); + if(_fs.exists(filename)){ + request->send(200); + } else { + fs::File f = _fs.open(filename, "w"); + if(f){ + f.write((uint8_t)0x00); + f.close(); + request->send(200, "", "CREATE: "+filename); + } else { + request->send(500); + } + } + } else + request->send(400); + } +} + +void SPIFFSEditor::handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ + if(!index){ + if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){ + _authenticated = true; + request->_tempFile = _fs.open(filename, "w"); + _startTime = millis(); + } + } + if(_authenticated && request->_tempFile){ + if(len){ + request->_tempFile.write(data,len); + } + if(final){ + request->_tempFile.close(); + } + } +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/SPIFFSEditor.h b/pixelart-controller/lib/ESPAsyncWebServer/src/SPIFFSEditor.h new file mode 100644 index 0000000..3586429 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/SPIFFSEditor.h @@ -0,0 +1,24 @@ +#ifndef SPIFFSEditor_H_ +#define SPIFFSEditor_H_ +#include + +class SPIFFSEditor: public AsyncWebHandler { + private: + fs::FS _fs; + String _username; + String _password; + bool _authenticated; + uint32_t _startTime; + public: +#ifdef ESP32 + SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String()); +#else + SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS); +#endif + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; + virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final; + virtual bool isRequestHandlerTrivial() override final {return false;} +}; + +#endif diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/StringArray.h b/pixelart-controller/lib/ESPAsyncWebServer/src/StringArray.h new file mode 100644 index 0000000..4c0aa70 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/StringArray.h @@ -0,0 +1,193 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef STRINGARRAY_H_ +#define STRINGARRAY_H_ + +#include "stddef.h" +#include "WString.h" + +template +class LinkedListNode { + T _value; + public: + LinkedListNode* next; + LinkedListNode(const T val): _value(val), next(nullptr) {} + ~LinkedListNode(){} + const T& value() const { return _value; }; + T& value(){ return _value; } +}; + +template class Item = LinkedListNode> +class LinkedList { + public: + typedef Item ItemType; + typedef std::function OnRemove; + typedef std::function Predicate; + private: + ItemType* _root; + OnRemove _onRemove; + + class Iterator { + ItemType* _node; + public: + Iterator(ItemType* current = nullptr) : _node(current) {} + Iterator(const Iterator& i) : _node(i._node) {} + Iterator& operator ++() { _node = _node->next; return *this; } + bool operator != (const Iterator& i) const { return _node != i._node; } + const T& operator * () const { return _node->value(); } + const T* operator -> () const { return &_node->value(); } + }; + + public: + typedef const Iterator ConstIterator; + ConstIterator begin() const { return ConstIterator(_root); } + ConstIterator end() const { return ConstIterator(nullptr); } + + LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} + ~LinkedList(){} + void add(const T& t){ + auto it = new ItemType(t); + if(!_root){ + _root = it; + } else { + auto i = _root; + while(i->next) i = i->next; + i->next = it; + } + } + T& front() const { + return _root->value(); + } + + bool isEmpty() const { + return _root == nullptr; + } + size_t length() const { + size_t i = 0; + auto it = _root; + while(it){ + i++; + it = it->next; + } + return i; + } + size_t count_if(Predicate predicate) const { + size_t i = 0; + auto it = _root; + while(it){ + if (!predicate){ + i++; + } + else if (predicate(it->value())) { + i++; + } + it = it->next; + } + return i; + } + const T* nth(size_t N) const { + size_t i = 0; + auto it = _root; + while(it){ + if(i++ == N) + return &(it->value()); + it = it->next; + } + return nullptr; + } + bool remove(const T& t){ + auto it = _root; + auto pit = _root; + while(it){ + if(it->value() == t){ + if(it == _root){ + _root = _root->next; + } else { + pit->next = it->next; + } + + if (_onRemove) { + _onRemove(it->value()); + } + + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + bool remove_first(Predicate predicate){ + auto it = _root; + auto pit = _root; + while(it){ + if(predicate(it->value())){ + if(it == _root){ + _root = _root->next; + } else { + pit->next = it->next; + } + if (_onRemove) { + _onRemove(it->value()); + } + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + + void free(){ + while(_root != nullptr){ + auto it = _root; + _root = _root->next; + if (_onRemove) { + _onRemove(it->value()); + } + delete it; + } + _root = nullptr; + } +}; + + +class StringArray : public LinkedList { +public: + + StringArray() : LinkedList(nullptr) {} + + bool containsIgnoreCase(const String& str){ + for (const auto& s : *this) { + if (str.equalsIgnoreCase(s)) { + return true; + } + } + return false; + } +}; + + + + +#endif /* STRINGARRAY_H_ */ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebAuthentication.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/WebAuthentication.cpp new file mode 100644 index 0000000..f96dc7e --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebAuthentication.cpp @@ -0,0 +1,235 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "WebAuthentication.h" +#include +#ifdef ESP32 +#include "mbedtls/md5.h" +#else +#include "md5.h" +#endif + + +// Basic Auth hash = base64("username:password") + +bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ + if(username == NULL || password == NULL || hash == NULL) + return false; + + size_t toencodeLen = strlen(username)+strlen(password)+1; + size_t encodedLen = base64_encode_expected_len(toencodeLen); + if(strlen(hash) != encodedLen) + return false; + + char *toencode = new char[toencodeLen+1]; + if(toencode == NULL){ + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + delete[] toencode; + return false; + } + sprintf(toencode, "%s:%s", username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + return false; +} + +static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more +#ifdef ESP32 + mbedtls_md5_context _ctx; +#else + md5_context_t _ctx; +#endif + uint8_t i; + uint8_t * _buf = (uint8_t*)malloc(16); + if(_buf == NULL) + return false; + memset(_buf, 0x00, 16); +#ifdef ESP32 + mbedtls_md5_init(&_ctx); + mbedtls_md5_update_ret (&_ctx,data,len); + mbedtls_md5_finish_ret(&_ctx,data); + mbedtls_internal_md5_process(&_ctx ,data); +#else + MD5Init(&_ctx); + MD5Update(&_ctx, data, len); + MD5Final(_buf, &_ctx); +#endif + for(i = 0; i < 16; i++) { + sprintf(output + (i * 2), "%02x", _buf[i]); + } + free(_buf); + return true; +} + +static String genRandomMD5(){ +#ifdef ESP8266 + uint32_t r = RANDOM_REG32; +#else + uint32_t r = rand(); +#endif + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) + return ""; + String res = String(out); + free(out); + return res; +} + +static String stringMD5(const String& in){ + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return ""; + String res = String(out); + free(out); + return res; +} + +String generateDigestHash(const char * username, const char * password, const char * realm){ + if(username == NULL || password == NULL || realm == NULL){ + return ""; + } + char * out = (char*)malloc(33); + String res = String(username); + res.concat(":"); + res.concat(realm); + res.concat(":"); + String in = res; + in.concat(password); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return ""; + res.concat(out); + free(out); + return res; +} + +String requestDigestAuthentication(const char * realm){ + String header = "realm=\""; + if(realm == NULL) + header.concat("asyncesp"); + else + header.concat(realm); + header.concat( "\", qop=\"auth\", nonce=\""); + header.concat(genRandomMD5()); + header.concat("\", opaque=\""); + header.concat(genRandomMD5()); + header.concat("\""); + return header; +} + +bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ + if(username == NULL || password == NULL || header == NULL || method == NULL){ + //os_printf("AUTH FAIL: missing requred fields\n"); + return false; + } + + String myHeader = String(header); + int nextBreak = myHeader.indexOf(","); + if(nextBreak < 0){ + //os_printf("AUTH FAIL: no variables\n"); + return false; + } + + String myUsername = String(); + String myRealm = String(); + String myNonce = String(); + String myUri = String(); + String myResponse = String(); + String myQop = String(); + String myNc = String(); + String myCnonce = String(); + + myHeader += ", "; + do { + String avLine = myHeader.substring(0, nextBreak); + avLine.trim(); + myHeader = myHeader.substring(nextBreak+1); + nextBreak = myHeader.indexOf(","); + + int eqSign = avLine.indexOf("="); + if(eqSign < 0){ + //os_printf("AUTH FAIL: no = sign\n"); + return false; + } + String varName = avLine.substring(0, eqSign); + avLine = avLine.substring(eqSign + 1); + if(avLine.startsWith("\"")){ + avLine = avLine.substring(1, avLine.length() - 1); + } + + if(varName.equals("username")){ + if(!avLine.equals(username)){ + //os_printf("AUTH FAIL: username\n"); + return false; + } + myUsername = avLine; + } else if(varName.equals("realm")){ + if(realm != NULL && !avLine.equals(realm)){ + //os_printf("AUTH FAIL: realm\n"); + return false; + } + myRealm = avLine; + } else if(varName.equals("nonce")){ + if(nonce != NULL && !avLine.equals(nonce)){ + //os_printf("AUTH FAIL: nonce\n"); + return false; + } + myNonce = avLine; + } else if(varName.equals("opaque")){ + if(opaque != NULL && !avLine.equals(opaque)){ + //os_printf("AUTH FAIL: opaque\n"); + return false; + } + } else if(varName.equals("uri")){ + if(uri != NULL && !avLine.equals(uri)){ + //os_printf("AUTH FAIL: uri\n"); + return false; + } + myUri = avLine; + } else if(varName.equals("response")){ + myResponse = avLine; + } else if(varName.equals("qop")){ + myQop = avLine; + } else if(varName.equals("nc")){ + myNc = avLine; + } else if(varName.equals("cnonce")){ + myCnonce = avLine; + } + } while(nextBreak > 0); + + String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ":" + myRealm + ":" + String(password)); + String ha2 = String(method) + ":" + myUri; + String response = ha1 + ":" + myNonce + ":" + myNc + ":" + myCnonce + ":" + myQop + ":" + stringMD5(ha2); + + if(myResponse.equals(stringMD5(response))){ + //os_printf("AUTH SUCCESS\n"); + return true; + } + + //os_printf("AUTH FAIL: password\n"); + return false; +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebAuthentication.h b/pixelart-controller/lib/ESPAsyncWebServer/src/WebAuthentication.h new file mode 100644 index 0000000..ff68265 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebAuthentication.h @@ -0,0 +1,34 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WEB_AUTHENTICATION_H_ +#define WEB_AUTHENTICATION_H_ + +#include "Arduino.h" + +bool checkBasicAuthentication(const char * header, const char * username, const char * password); +String requestDigestAuthentication(const char * realm); +bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); + +//for storing hashed versions on the device that can be authenticated against +String generateDigestHash(const char * username, const char * password, const char * realm); + +#endif diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebHandlerImpl.h b/pixelart-controller/lib/ESPAsyncWebServer/src/WebHandlerImpl.h new file mode 100644 index 0000000..9b7ba1b --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebHandlerImpl.h @@ -0,0 +1,151 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ +#define ASYNCWEBSERVERHANDLERIMPL_H_ + +#include +#ifdef ASYNCWEBSERVER_REGEX +#include +#endif + +#include "stddef.h" +#include + +class AsyncStaticWebHandler: public AsyncWebHandler { + using File = fs::File; + using FS = fs::FS; + private: + bool _getFile(AsyncWebServerRequest *request); + bool _fileExists(AsyncWebServerRequest *request, const String& path); + uint8_t _countBits(const uint8_t value) const; + protected: + FS _fs; + String _uri; + String _path; + String _default_file; + String _cache_control; + String _last_modified; + AwsTemplateProcessor _callback; + bool _isDir; + bool _gzipFirst; + uint8_t _gzipStats; + public: + AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; + AsyncStaticWebHandler& setIsDir(bool isDir); + AsyncStaticWebHandler& setDefaultFile(const char* filename); + AsyncStaticWebHandler& setCacheControl(const char* cache_control); + AsyncStaticWebHandler& setLastModified(const char* last_modified); + AsyncStaticWebHandler& setLastModified(struct tm* last_modified); + #ifdef ESP8266 + AsyncStaticWebHandler& setLastModified(time_t last_modified); + AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated + #endif + AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} +}; + +class AsyncCallbackWebHandler: public AsyncWebHandler { + private: + protected: + String _uri; + WebRequestMethodComposite _method; + ArRequestHandlerFunction _onRequest; + ArUploadHandlerFunction _onUpload; + ArBodyHandlerFunction _onBody; + bool _isRegex; + public: + AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} + void setUri(const String& uri){ + _uri = uri; + _isRegex = uri.startsWith("^") && uri.endsWith("$"); + } + void setMethod(WebRequestMethodComposite method){ _method = method; } + void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } + void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } + void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } + + virtual bool canHandle(AsyncWebServerRequest *request) override final{ + + if(!_onRequest) + return false; + + if(!(_method & request->method())) + return false; + +#ifdef ASYNCWEBSERVER_REGEX + if (_isRegex) { + std::regex pattern(_uri.c_str()); + std::smatch matches; + std::string s(request->url().c_str()); + if(std::regex_search(s, matches, pattern)) { + for (size_t i = 1; i < matches.size(); ++i) { // start from 1 + request->_addPathParam(matches[i].str().c_str()); + } + } else { + return false; + } + } else +#endif + if (_uri.length() && _uri.startsWith("/*.")) { + String uriTemplate = String (_uri); + uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); + if (!request->url().endsWith(uriTemplate)) + return false; + } + else + if (_uri.length() && _uri.endsWith("*")) { + String uriTemplate = String(_uri); + uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); + if (!request->url().startsWith(uriTemplate)) + return false; + } + else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) + return false; + + request->addInterestingHeader("ANY"); + return true; + } + + virtual void handleRequest(AsyncWebServerRequest *request) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onRequest) + _onRequest(request); + else + request->send(500); + } + virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onUpload) + _onUpload(request, filename, index, data, len, final); + } + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onBody) + _onBody(request, data, len, index, total); + } + virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} +}; + +#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebHandlers.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/WebHandlers.cpp new file mode 100644 index 0000000..1f435e6 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebHandlers.cpp @@ -0,0 +1,220 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebHandlerImpl.h" + +AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) + : _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr) +{ + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; + + // If path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if it does not end with '/' we, can't assume a file, path can still be a directory. + _isDir = _path[_path.length()-1] == '/'; + + // Remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); + if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); + + // Reset stats + _gzipFirst = false; + _gzipStats = 0xF8; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ + _isDir = isDir; + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ + _default_file = String(filename); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ + _cache_control = String(cache_control); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ + _last_modified = String(last_modified); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ + char result[30]; + strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); + return setLastModified((const char *)result); +} + +#ifdef ESP8266 +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ + return setLastModified((struct tm *)gmtime(&last_modified)); +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ + time_t last_modified; + if(time(&last_modified) == 0) //time is not yet set + return *this; + return setLastModified(last_modified); +} +#endif +bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET + || !request->url().startsWith(_uri) + || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) + ){ + return false; + } + if (_getFile(request)) { + // We interested in "If-Modified-Since" header to check if file was modified + if (_last_modified.length()) + request->addInterestingHeader("If-Modified-Since"); + + if(_cache_control.length()) + request->addInterestingHeader("If-None-Match"); + + DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); + return true; + } + + return false; +} + +bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) +{ + // Remove the found uri + String path = request->url().substring(_uri.length()); + + // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); + + path = _path + path; + + // Do we have a file or .gz file + if (!canSkipFileCheck && _fileExists(request, path)) + return true; + + // Can't handle if not default file + if (_default_file.length() == 0) + return false; + + // Try to add default file, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length()-1] != '/') + path += "/"; + path += _default_file; + + return _fileExists(request, path); +} + +#ifdef ESP32 +#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) +#else +#define FILE_IS_REAL(f) (f == true) +#endif + +bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) +{ + bool fileFound = false; + bool gzipFound = false; + + String gzip = path + ".gz"; + + if (_gzipFirst) { + request->_tempFile = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(request->_tempFile); + if (!gzipFound){ + request->_tempFile = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(request->_tempFile); + } + } else { + request->_tempFile = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(request->_tempFile); + if (!fileFound){ + request->_tempFile = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(request->_tempFile); + } + } + + bool found = fileFound || gzipFound; + + if (found) { + // Extract the file name from the path and keep it in _tempObject + size_t pathLen = path.length(); + char * _tempPath = (char*)malloc(pathLen+1); + snprintf(_tempPath, pathLen+1, "%s", path.c_str()); + request->_tempObject = (void*)_tempPath; + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip + else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } + + return found; +} + +uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const +{ + uint8_t w = value; + uint8_t n; + for (n=0; w!=0; n++) w&=w-1; + return n; +} + +void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) +{ + // Get the filename from request->_tempObject and free it + String filename = String((char*)request->_tempObject); + free(request->_tempObject); + request->_tempObject = NULL; + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + + if (request->_tempFile == true) { + String etag = String(request->_tempFile.size()); + if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { + request->_tempFile.close(); + request->send(304); // Not modified + } else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { + request->_tempFile.close(); + AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified + response->addHeader("Cache-Control", _cache_control); + response->addHeader("ETag", etag); + request->send(response); + } else { + AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); + if (_last_modified.length()) + response->addHeader("Last-Modified", _last_modified); + if (_cache_control.length()){ + response->addHeader("Cache-Control", _cache_control); + response->addHeader("ETag", etag); + } + request->send(response); + } + } else { + request->send(404); + } +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebRequest.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/WebRequest.cpp new file mode 100644 index 0000000..bbce5ca --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebRequest.cpp @@ -0,0 +1,1008 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "WebAuthentication.h" + +#ifndef ESP8266 +#define os_strlen strlen +#endif + +static const String SharedEmptyString = String(); + +#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) + +enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; + +AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) + : _client(c) + , _server(s) + , _handler(NULL) + , _response(NULL) + , _temp() + , _parseState(0) + , _version(0) + , _method(HTTP_ANY) + , _url() + , _host() + , _contentType() + , _boundary() + , _authorization() + , _reqconntype(RCT_HTTP) + , _isDigest(false) + , _isMultipart(false) + , _isPlainPost(false) + , _expectingContinue(false) + , _contentLength(0) + , _parsedLength(0) + , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) + , _params(LinkedList([](AsyncWebParameter *p){ delete p; })) + , _pathParams(LinkedList([](String *p){ delete p; })) + , _multiParseState(0) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) + , _tempObject(NULL) +{ + c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); + c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); + c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); + c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); + c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); + c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); +} + +AsyncWebServerRequest::~AsyncWebServerRequest(){ + _headers.free(); + + _params.free(); + _pathParams.free(); + + _interestingHeaders.free(); + + if(_response != NULL){ + delete _response; + } + + if(_tempObject != NULL){ + free(_tempObject); + } + + if(_tempFile){ + _tempFile.close(); + } +} + +void AsyncWebServerRequest::_onData(void *buf, size_t len){ + size_t i = 0; + while (true) { + + if(_parseState < PARSE_REQ_BODY){ + // Find new line in buf + char *str = (char*)buf; + for (i = 0; i < len; i++) { + if (str[i] == '\n') { + break; + } + } + if (i == len) { // No new line, just add the buffer in _temp + char ch = str[len-1]; + str[len-1] = 0; + _temp.reserve(_temp.length()+len); + _temp.concat(str); + _temp.concat(ch); + } else { // Found new line - extract it and parse + str[i] = 0; // Terminate the string at the end of the line. + _temp.concat(str); + _temp.trim(); + _parseLine(); + if (++i < len) { + // Still have more buffer to process + buf = str+i; + len-= i; + continue; + } + } + } else if(_parseState == PARSE_REQ_BODY){ + // A handler should be already attached at this point in _parseLine function. + // If handler does nothing (_onRequest is NULL), we don't need to really parse the body. + const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); + if(_isMultipart){ + if(needParse){ + size_t i; + for(i=0; i end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value))); + start = end + 1; + } +} + +bool AsyncWebServerRequest::_parseReqHead(){ + // Split the head into method, url and version + int index = _temp.indexOf(' '); + String m = _temp.substring(0, index); + index = _temp.indexOf(' ', index+1); + String u = _temp.substring(m.length()+1, index); + _temp = _temp.substring(index+1); + + if(m == "GET"){ + _method = HTTP_GET; + } else if(m == "POST"){ + _method = HTTP_POST; + } else if(m == "DELETE"){ + _method = HTTP_DELETE; + } else if(m == "PUT"){ + _method = HTTP_PUT; + } else if(m == "PATCH"){ + _method = HTTP_PATCH; + } else if(m == "HEAD"){ + _method = HTTP_HEAD; + } else if(m == "OPTIONS"){ + _method = HTTP_OPTIONS; + } + + String g = String(); + index = u.indexOf('?'); + if(index > 0){ + g = u.substring(index +1); + u = u.substring(0, index); + } + _url = urlDecode(u); + _addGetParams(g); + + if(!_temp.startsWith("HTTP/1.0")) + _version = 1; + + _temp = String(); + return true; +} + +bool strContains(String src, String find, bool mindcase = true) { + int pos=0, i=0; + const int slen = src.length(); + const int flen = find.length(); + + if (slen < flen) return false; + while (pos <= (slen - flen)) { + for (i=0; i < flen; i++) { + if (mindcase) { + if (src[pos+i] != find[i]) i = flen + 1; // no match + } else if (tolower(src[pos+i]) != tolower(find[i])) i = flen + 1; // no match + } + if (i == flen) return true; + pos++; + } + return false; +} + +bool AsyncWebServerRequest::_parseReqHeader(){ + int index = _temp.indexOf(':'); + if(index){ + String name = _temp.substring(0, index); + String value = _temp.substring(index + 2); + if(name.equalsIgnoreCase("Host")){ + _host = value; + } else if(name.equalsIgnoreCase("Content-Type")){ + _contentType = value.substring(0, value.indexOf(';')); + if (value.startsWith("multipart/")){ + _boundary = value.substring(value.indexOf('=')+1); + _boundary.replace("\"",""); + _isMultipart = true; + } + } else if(name.equalsIgnoreCase("Content-Length")){ + _contentLength = atoi(value.c_str()); + } else if(name.equalsIgnoreCase("Expect") && value == "100-continue"){ + _expectingContinue = true; + } else if(name.equalsIgnoreCase("Authorization")){ + if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase("Basic")){ + _authorization = value.substring(6); + } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase("Digest")){ + _isDigest = true; + _authorization = value.substring(7); + } + } else { + if(name.equalsIgnoreCase("Upgrade") && value.equalsIgnoreCase("websocket")){ + // WebSocket request can be uniquely identified by header: [Upgrade: websocket] + _reqconntype = RCT_WS; + } else { + if(name.equalsIgnoreCase("Accept") && strContains(value, "text/event-stream", false)){ + // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] + _reqconntype = RCT_EVENT; + } + } + } + _headers.add(new AsyncWebHeader(name, value)); + } + _temp = String(); + return true; +} + +void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ + if(data && (char)data != '&') + _temp += (char)data; + if(!data || (char)data == '&' || _parsedLength == _contentLength){ + String name = "body"; + String value = _temp; + if(!_temp.startsWith("{") && !_temp.startsWith("[") && _temp.indexOf('=') > 0){ + name = _temp.substring(0, _temp.indexOf('=')); + value = _temp.substring(_temp.indexOf('=') + 1); + } + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true)); + _temp = String(); + } +} + +void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){ + _itemBuffer[_itemBufferIndex++] = data; + + if(last || _itemBufferIndex == 1460){ + //check if authenticated before calling the upload + if(_handler) + _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); + _itemBufferIndex = 0; + } +} + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){ +#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0) + + if(!_parsedLength){ + _multiParseState = EXPECT_BOUNDARY; + _temp = String(); + _itemName = String(); + _itemFilename = String(); + _itemType = String(); + } + + if(_multiParseState == WAIT_FOR_RETURN1){ + if(data != '\r'){ + itemWriteByte(data); + } else { + _multiParseState = EXPECT_FEED1; + } + } else if(_multiParseState == EXPECT_BOUNDARY){ + if(_parsedLength < 2 && data != '-'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 3 == _boundary.length()){ + if(data != '\n'){ + _multiParseState = PARSE_ERROR; + return; + } + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } + } else if(_multiParseState == PARSE_HEADERS){ + if((char)data != '\r' && (char)data != '\n') + _temp += (char)data; + if((char)data == '\n'){ + if(_temp.length()){ + if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + free(_itemBuffer); + _itemBuffer = (uint8_t*)malloc(1460); + if(_itemBuffer == NULL){ + _multiParseState = PARSE_ERROR; + return; + } + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + //check if authenticated before calling the upload + if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + free(_itemBuffer); + _itemBuffer = NULL; + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '-' && (_contentLength - _parsedLength - 4) != 0){ + //os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); + _contentLength = _parsedLength + 4;//lets close the request gracefully + } + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _contentLength == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } + } +} + +void AsyncWebServerRequest::_parseLine(){ + if(_parseState == PARSE_REQ_START){ + if(!_temp.length()){ + _parseState = PARSE_REQ_FAIL; + _client->close(); + } else { + _parseReqHead(); + _parseState = PARSE_REQ_HEADERS; + } + return; + } + + if(_parseState == PARSE_REQ_HEADERS){ + if(!_temp.length()){ + //end of headers + _server->_rewriteRequest(this); + _server->_attachHandler(this); + _removeNotInterestingHeaders(); + if(_expectingContinue){ + const char * response = "HTTP/1.1 100 Continue\r\n\r\n"; + _client->write(response, os_strlen(response)); + } + //check handler for authentication + if(_contentLength){ + _parseState = PARSE_REQ_BODY; + } else { + _parseState = PARSE_REQ_END; + if(_handler) _handler->handleRequest(this); + else send(501); + } + } else _parseReqHeader(); + } +} + +size_t AsyncWebServerRequest::headers() const{ + return _headers.length(); +} + +bool AsyncWebServerRequest::hasHeader(const String& name) const { + for(const auto& h: _headers){ + if(h->name().equalsIgnoreCase(name)){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const { + PGM_P p = reinterpret_cast(data); + size_t n = 0; + while (1) { + if (pgm_read_byte(p+n) == 0) break; + n += 1; + } + char * name = (char*) malloc(n+1); + name[n] = 0; + if (name) { + for(size_t b=0; bname().equalsIgnoreCase(name)){ + return h; + } + } + return nullptr; +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + AsyncWebHeader* result = getHeader( String(name)); + free(name); + return result; + } else { + return nullptr; + } +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { + auto header = _headers.nth(num); + return header ? *header : nullptr; +} + +size_t AsyncWebServerRequest::params() const { + return _params.length(); +} + +bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + + char * name = (char*) malloc(n+1); + name[n] = 0; + if (name) { + strcpy_P(name,p); + bool result = hasParam( name, post, file); + free(name); + return result; + } else { + return false; + } +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return p; + } + } + return nullptr; +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + AsyncWebParameter* result = getParam(name, post, file); + free(name); + return result; + } else { + return nullptr; + } +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { + auto param = _params.nth(num); + return param ? *param : nullptr; +} + +void AsyncWebServerRequest::addInterestingHeader(const String& name){ + if(!_interestingHeaders.containsIgnoreCase(name)) + _interestingHeaders.add(name); +} + +void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ + _response = response; + if(_response == NULL){ + _client->close(true); + _onDisconnect(); + return; + } + if(!_response->_sourceValid()){ + delete response; + _response = NULL; + send(500); + } + else { + _client->setRxTimeout(0); + _response->_respond(this); + } +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){ + return new AsyncBasicResponse(code, contentType, content); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+".gz"))) + return new AsyncFileResponse(fs, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true) + return new AsyncFileResponse(content, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + return new AsyncStreamResponse(stream, contentType, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + return new AsyncCallbackResponse(contentType, len, callback, templateCallback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + if(_version) + return new AsyncChunkedResponse(contentType, callback, templateCallback); + return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); +} + +AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){ + return new AsyncResponseStream(contentType, bufferSize); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + return new AsyncProgmemResponse(code, contentType, content, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); +} + +void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){ + send(beginResponse(code, contentType, content)); +} + +void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+".gz"))){ + send(beginResponse(fs, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true){ + send(beginResponse(content, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + send(beginResponse(stream, contentType, len, callback)); +} + +void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginResponse(contentType, len, callback, templateCallback)); +} + +void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginChunkedResponse(contentType, callback, templateCallback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, len, callback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, callback)); +} + +void AsyncWebServerRequest::redirect(const String& url){ + AsyncWebServerResponse * response = beginResponse(302); + response->addHeader("Location",url); + send(response); +} + +bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){ + if(_authorization.length()){ + if(_isDigest) + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); + else if(!passwordIsHash) + return checkBasicAuthentication(_authorization.c_str(), username, password); + else + return _authorization.equals(password); + } + return false; +} + +bool AsyncWebServerRequest::authenticate(const char * hash){ + if(!_authorization.length() || hash == NULL) + return false; + + if(_isDigest){ + String hStr = String(hash); + int separator = hStr.indexOf(":"); + if(separator <= 0) + return false; + String username = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + separator = hStr.indexOf(":"); + if(separator <= 0) + return false; + String realm = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); + } + + return (_authorization.equals(hash)); +} + +void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){ + AsyncWebServerResponse * r = beginResponse(401); + if(!isDigest && realm == NULL){ + r->addHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); + } else if(!isDigest){ + String header = "Basic realm=\""; + header.concat(realm); + header.concat("\""); + r->addHeader("WWW-Authenticate", header); + } else { + String header = "Digest "; + header.concat(requestDigestAuthentication(realm)); + r->addHeader("WWW-Authenticate", header); + } + send(r); +} + +bool AsyncWebServerRequest::hasArg(const char* name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + bool result = hasArg( name ); + free(name); + return result; + } else { + return false; + } +} + + +const String& AsyncWebServerRequest::arg(const String& name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return arg->value(); + } + } + return SharedEmptyString; +} + +const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + const String & result = arg( String(name) ); + free(name); + return result; + } else { + return SharedEmptyString; + } + +} + +const String& AsyncWebServerRequest::arg(size_t i) const { + return getParam(i)->value(); +} + +const String& AsyncWebServerRequest::argName(size_t i) const { + return getParam(i)->name(); +} + +const String& AsyncWebServerRequest::pathArg(size_t i) const { + auto param = _pathParams.nth(i); + return param ? **param : SharedEmptyString; +} + +const String& AsyncWebServerRequest::header(const char* name) const { + AsyncWebHeader* h = getHeader(String(name)); + return h ? h->value() : SharedEmptyString; +} + +const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + const String & result = header( (const char *)name ); + free(name); + return result; + } else { + return SharedEmptyString; + } +}; + + +const String& AsyncWebServerRequest::header(size_t i) const { + AsyncWebHeader* h = getHeader(i); + return h ? h->value() : SharedEmptyString; +} + +const String& AsyncWebServerRequest::headerName(size_t i) const { + AsyncWebHeader* h = getHeader(i); + return h ? h->name() : SharedEmptyString; +} + +String AsyncWebServerRequest::urlDecode(const String& text) const { + char temp[] = "0x00"; + unsigned int len = text.length(); + unsigned int i = 0; + String decoded = String(); + decoded.reserve(len); // Allocate the string internal buffer - never longer from source text + while (i < len){ + char decodedChar; + char encodedChar = text.charAt(i++); + if ((encodedChar == '%') && (i + 1 < len)){ + temp[2] = text.charAt(i++); + temp[3] = text.charAt(i++); + decodedChar = strtol(temp, NULL, 16); + } else if (encodedChar == '+') { + decodedChar = ' '; + } else { + decodedChar = encodedChar; // normal ascii char + } + decoded.concat(decodedChar); + } + return decoded; +} + + +const char * AsyncWebServerRequest::methodToString() const { + if(_method == HTTP_ANY) return "ANY"; + else if(_method & HTTP_GET) return "GET"; + else if(_method & HTTP_POST) return "POST"; + else if(_method & HTTP_DELETE) return "DELETE"; + else if(_method & HTTP_PUT) return "PUT"; + else if(_method & HTTP_PATCH) return "PATCH"; + else if(_method & HTTP_HEAD) return "HEAD"; + else if(_method & HTTP_OPTIONS) return "OPTIONS"; + return "UNKNOWN"; +} + +const char *AsyncWebServerRequest::requestedConnTypeToString() const { + switch (_reqconntype) { + case RCT_NOT_USED: return "RCT_NOT_USED"; + case RCT_DEFAULT: return "RCT_DEFAULT"; + case RCT_HTTP: return "RCT_HTTP"; + case RCT_WS: return "RCT_WS"; + case RCT_EVENT: return "RCT_EVENT"; + default: return "ERROR"; + } +} + +bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) { + bool res = false; + if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true; + if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true; + if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true; + return res; +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebResponseImpl.h b/pixelart-controller/lib/ESPAsyncWebServer/src/WebResponseImpl.h new file mode 100644 index 0000000..9a64e3a --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebResponseImpl.h @@ -0,0 +1,136 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ +#define ASYNCWEBSERVERRESPONSEIMPL_H_ + +#ifdef Arduino_h +// arduino is not compatible with std::vector +#undef min +#undef max +#endif +#include +// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. + +class AsyncBasicResponse: public AsyncWebServerResponse { + private: + String _content; + public: + AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + +class AsyncAbstractResponse: public AsyncWebServerResponse { + private: + String _head; + // Data is inserted into cache at begin(). + // This is inefficient with vector, but if we use some other container, + // we won't be able to access it as contiguous array of bytes when reading from it, + // so by gaining performance in one place, we'll lose it in another. + std::vector _cache; + size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); + size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); + protected: + AwsTemplateProcessor _callback; + public: + AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return false; } + virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } +}; + +#ifndef TEMPLATE_PLACEHOLDER +#define TEMPLATE_PLACEHOLDER '%' +#endif + +#define TEMPLATE_PARAM_NAME_LENGTH 32 +class AsyncFileResponse: public AsyncAbstractResponse { + using File = fs::File; + using FS = fs::FS; + private: + File _content; + String _path; + void _setContentType(const String& path); + public: + AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + ~AsyncFileResponse(); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncStreamResponse: public AsyncAbstractResponse { + private: + Stream *_content; + public: + AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncCallbackResponse: public AsyncAbstractResponse { + private: + AwsResponseFiller _content; + size_t _filledLength; + public: + AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncChunkedResponse: public AsyncAbstractResponse { + private: + AwsResponseFiller _content; + size_t _filledLength; + public: + AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncProgmemResponse: public AsyncAbstractResponse { + private: + const uint8_t * _content; + size_t _readLength; + public: + AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + bool _sourceValid() const { return true; } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class cbuf; + +class AsyncResponseStream: public AsyncAbstractResponse, public Print { + private: + cbuf *_content; + public: + AsyncResponseStream(const String& contentType, size_t bufferSize); + ~AsyncResponseStream(); + bool _sourceValid() const { return (_state < RESPONSE_END); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data); + using Print::write; +}; + +#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebResponses.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/WebResponses.cpp new file mode 100644 index 0000000..a22e991 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebResponses.cpp @@ -0,0 +1,699 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "cbuf.h" + +// Since ESP8266 does not link memchr by default, here's its implementation. +void* memchr(void* ptr, int ch, size_t count) +{ + unsigned char* p = static_cast(ptr); + while(count--) + if(*p++ == static_cast(ch)) + return --p; + return nullptr; +} + + +/* + * Abstract Response + * */ +const char* AsyncWebServerResponse::_responseCodeToString(int code) { + switch (code) { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Time-out"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Large"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested range not satisfiable"; + case 417: return "Expectation Failed"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Time-out"; + case 505: return "HTTP Version not supported"; + default: return ""; + } +} + +AsyncWebServerResponse::AsyncWebServerResponse() + : _code(0) + , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) + , _contentType() + , _contentLength(0) + , _sendContentLength(true) + , _chunked(false) + , _headLength(0) + , _sentLength(0) + , _ackedLength(0) + , _writtenLength(0) + , _state(RESPONSE_SETUP) +{ + for(auto header: DefaultHeaders::Instance()) { + _headers.add(new AsyncWebHeader(header->name(), header->value())); + } +} + +AsyncWebServerResponse::~AsyncWebServerResponse(){ + _headers.free(); +} + +void AsyncWebServerResponse::setCode(int code){ + if(_state == RESPONSE_SETUP) + _code = code; +} + +void AsyncWebServerResponse::setContentLength(size_t len){ + if(_state == RESPONSE_SETUP) + _contentLength = len; +} + +void AsyncWebServerResponse::setContentType(const String& type){ + if(_state == RESPONSE_SETUP) + _contentType = type; +} + +void AsyncWebServerResponse::addHeader(const String& name, const String& value){ + _headers.add(new AsyncWebHeader(name, value)); +} + +String AsyncWebServerResponse::_assembleHead(uint8_t version){ + if(version){ + addHeader("Accept-Ranges","none"); + if(_chunked) + addHeader("Transfer-Encoding","chunked"); + } + String out = String(); + int bufSize = 300; + char buf[bufSize]; + + snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code)); + out.concat(buf); + + if(_sendContentLength) { + snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength); + out.concat(buf); + } + if(_contentType.length()) { + snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str()); + out.concat(buf); + } + + for(const auto& header: _headers){ + snprintf(buf, bufSize, "%s: %s\r\n", header->name().c_str(), header->value().c_str()); + out.concat(buf); + } + _headers.free(); + + out.concat("\r\n"); + _headLength = out.length(); + return out; +} + +bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } +bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } +bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } +bool AsyncWebServerResponse::_sourceValid() const { return false; } +void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } +size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } + +/* + * String/Code Response + * */ +AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ + _code = code; + _content = content; + _contentType = contentType; + if(_content.length()){ + _contentLength = _content.length(); + if(!_contentType.length()) + _contentType = "text/plain"; + } + addHeader("Connection","close"); +} + +void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ + _state = RESPONSE_HEADERS; + String out = _assembleHead(request->version()); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if(!_contentLength && space >= outLen){ + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(_contentLength && space >= outLen + _contentLength){ + out += _content; + outLen += _contentLength; + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(space && space < outLen){ + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if(space > outLen && space < (outLen + _contentLength)){ + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } +} + +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + _ackedLength += len; + if(_state == RESPONSE_CONTENT){ + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if(space > available){ + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + _writtenLength += request->client()->write(out.c_str(), space); + return space; + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= _writtenLength){ + _state = RESPONSE_END; + } + } + return 0; +} + + +/* + * Abstract Response + * */ + +AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) +{ + // In case of template processing, we're unable to determine real response size + if(callback) { + _contentLength = 0; + _sendContentLength = false; + _chunked = true; + } +} + +void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ + addHeader("Connection","close"); + _head = _assembleHead(request->version()); + _state = RESPONSE_HEADERS; + _ack(request, 0, 0); +} + +size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + if(!_sourceValid()){ + _state = RESPONSE_FAILED; + request->client()->close(); + return 0; + } + _ackedLength += len; + size_t space = request->client()->space(); + + size_t headLen = _head.length(); + if(_state == RESPONSE_HEADERS){ + if(space >= headLen){ + _state = RESPONSE_CONTENT; + space -= headLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + _writtenLength += request->client()->write(out.c_str(), out.length()); + return out.length(); + } + } + + if(_state == RESPONSE_CONTENT){ + size_t outLen; + if(_chunked){ + if(space <= 8){ + return 0; + } + outLen = space; + } else if(!_sendContentLength){ + outLen = space; + } else { + outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); + } + + uint8_t *buf = (uint8_t *)malloc(outLen+headLen); + if (!buf) { + // os_printf("_ack malloc %d failed\n", outLen+headLen); + return 0; + } + + if(headLen){ + memcpy(buf, _head.c_str(), _head.length()); + } + + size_t readLen = 0; + + if(_chunked){ + // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. + // See RFC2616 sections 2, 3.6.1. + readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen; + while(outLen < headLen + 4) buf[outLen++] = ' '; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + outLen += readLen; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + } else { + readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = readLen + headLen; + } + + if(headLen){ + _head = String(); + } + + if(outLen){ + _writtenLength += request->client()->write((const char*)buf, outLen); + } + + if(_chunked){ + _sentLength += readLen; + } else { + _sentLength += outLen - headLen; + } + + free(buf); + + if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ + _state = RESPONSE_WAIT_ACK; + } + return outLen; + + } else if(_state == RESPONSE_WAIT_ACK){ + if(!_sendContentLength || _ackedLength >= _writtenLength){ + _state = RESPONSE_END; + if(!_chunked && !_sendContentLength) + request->client()->close(true); + } + } + return 0; +} + +size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) +{ + // If we have something in cache, copy it to buffer + const size_t readFromCache = std::min(len, _cache.size()); + if(readFromCache) { + memcpy(data, _cache.data(), readFromCache); + _cache.erase(_cache.begin(), _cache.begin() + readFromCache); + } + // If we need to read more... + const size_t needFromFile = len - readFromCache; + const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); + return readFromCache + readFromContent; +} + +size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) +{ + if(!_callback) + return _fillBuffer(data, len); + + const size_t originalLen = len; + len = _readDataFromCacheOrContent(data, len); + // Now we've read 'len' bytes, either from cache or from file + // Search for template placeholders + uint8_t* pTemplateStart = data; + while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] + uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; + // temporary buffer to hold parameter name + uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; + String paramName; + // If closing placeholder is found: + if(pTemplateEnd) { + // prepare argument to callback + const size_t paramNameLength = std::min(sizeof(buf) - 1, (unsigned int)(pTemplateEnd - pTemplateStart - 1)); + if(paramNameLength) { + memcpy(buf, pTemplateStart + 1, paramNameLength); + buf[paramNameLength] = 0; + paramName = String(reinterpret_cast(buf)); + } else { // double percent sign encountered, this is single percent sign escaped. + // remove the 2nd percent sign + memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; + ++pTemplateStart; + } + } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data + memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); + const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); + if(readFromCacheOrContent) { + pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); + if(pTemplateEnd) { + // prepare argument to callback + *pTemplateEnd = 0; + paramName = String(reinterpret_cast(buf)); + // Copy remaining read-ahead data into cache + _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + pTemplateEnd = &data[len - 1]; + } + else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position + { + // but first, store read file data in cache + _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + ++pTemplateStart; + } + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + if(paramName.length()) { + // call callback and replace with result. + // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. + // Data after pTemplateEnd may need to be moved. + // The first byte of data after placeholder is located at pTemplateEnd + 1. + // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). + const String paramValue(_callback(paramName)); + const char* pvstr = paramValue.c_str(); + const unsigned int pvlen = paramValue.length(); + const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); + // make room for param value + // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store + if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { + _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); + //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); + len = originalLen; // fix issue with truncated data, not sure if it has any side effects + } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) + //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. + // Move the entire data after the placeholder + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + // 3. replace placeholder with actual value + memcpy(pTemplateStart, pvstr, numBytesCopied); + // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) + if(numBytesCopied < pvlen) { + _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); + } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... + // there is some free room, fill it from cache + const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; + const size_t totalFreeRoom = originalLen - len + roomFreed; + len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; + } else { // result is copied fully; it is longer than placeholder text + const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; + len = std::min(len + roomTaken, originalLen); + } + } + } // while(pTemplateStart) + return len; +} + + +/* + * File Response + * */ + +AsyncFileResponse::~AsyncFileResponse(){ + if(_content) + _content.close(); +} + +void AsyncFileResponse::_setContentType(const String& path){ + if (path.endsWith(".html")) _contentType = "text/html"; + else if (path.endsWith(".htm")) _contentType = "text/html"; + else if (path.endsWith(".css")) _contentType = "text/css"; + else if (path.endsWith(".json")) _contentType = "application/json"; + else if (path.endsWith(".js")) _contentType = "application/javascript"; + else if (path.endsWith(".png")) _contentType = "image/png"; + else if (path.endsWith(".gif")) _contentType = "image/gif"; + else if (path.endsWith(".jpg")) _contentType = "image/jpeg"; + else if (path.endsWith(".ico")) _contentType = "image/x-icon"; + else if (path.endsWith(".svg")) _contentType = "image/svg+xml"; + else if (path.endsWith(".eot")) _contentType = "font/eot"; + else if (path.endsWith(".woff")) _contentType = "font/woff"; + else if (path.endsWith(".woff2")) _contentType = "font/woff2"; + else if (path.endsWith(".ttf")) _contentType = "font/ttf"; + else if (path.endsWith(".xml")) _contentType = "text/xml"; + else if (path.endsWith(".pdf")) _contentType = "application/pdf"; + else if (path.endsWith(".zip")) _contentType = "application/zip"; + else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; + else _contentType = "text/plain"; +} + +AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ + _path = _path+".gz"; + addHeader("Content-Encoding", "gzip"); + _callback = nullptr; // Unable to process zipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = fs.open(_path, "r"); + _contentLength = _content.size(); + + if(contentType == "") + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + // set filename and force download + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + } else { + // set filename and force rendering + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){ + addHeader("Content-Encoding", "gzip"); + _callback = nullptr; // Unable to process gzipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = content; + _contentLength = _content.size(); + + if(contentType == "") + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + } else { + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ + return _content.read(data, len); +} + +/* + * Stream Response + * */ + +AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; +} + +size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t available = _content->available(); + size_t outLen = (available > len)?len:available; + size_t i; + for(i=0;iread(); + return outLen; +} + +/* + * Callback Response + * */ + +AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { + _code = 200; + _content = callback; + _contentLength = len; + if(!len) + _sendContentLength = false; + _contentType = contentType; + _filledLength = 0; +} + +size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Chunked Response + * */ + +AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { + _code = 200; + _content = callback; + _contentLength = 0; + _contentType = contentType; + _sendContentLength = false; + _chunked = true; + _filledLength = 0; +} + +size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Progmem Response + * */ + +AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = code; + _content = content; + _contentType = contentType; + _contentLength = len; + _readLength = 0; +} + +size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t left = _contentLength - _readLength; + if (left > len) { + memcpy_P(data, _content + _readLength, len); + _readLength += len; + return len; + } + memcpy_P(data, _content + _readLength, left); + _readLength += left; + return left; +} + + +/* + * Response Stream (You can print/write/printf to it, up to the contentLen bytes) + * */ + +AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ + _code = 200; + _contentLength = 0; + _contentType = contentType; + _content = new cbuf(bufferSize); +} + +AsyncResponseStream::~AsyncResponseStream(){ + delete _content; +} + +size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ + return _content->read((char*)buf, maxLen); +} + +size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ + if(_started()) + return 0; + + if(len > _content->room()){ + size_t needed = len - _content->room(); + _content->resizeAdd(needed); + } + size_t written = _content->write((const char*)data, len); + _contentLength += written; + return written; +} + +size_t AsyncResponseStream::write(uint8_t data){ + return write(&data, 1); +} diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/WebServer.cpp b/pixelart-controller/lib/ESPAsyncWebServer/src/WebServer.cpp new file mode 100644 index 0000000..95c2dd6 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/WebServer.cpp @@ -0,0 +1,193 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebHandlerImpl.h" + +bool ON_STA_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() == request->client()->localIP(); +} + +bool ON_AP_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() != request->client()->localIP(); +} + + +AsyncWebServer::AsyncWebServer(uint16_t port) + : _server(port) + , _rewrites(LinkedList([](AsyncWebRewrite* r){ delete r; })) + , _handlers(LinkedList([](AsyncWebHandler* h){ delete h; })) +{ + _catchAllHandler = new AsyncCallbackWebHandler(); + if(_catchAllHandler == NULL) + return; + _server.onClient([](void *s, AsyncClient* c){ + if(c == NULL) + return; + c->setRxTimeout(3); + AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); + if(r == NULL){ + c->close(true); + c->free(); + delete c; + } + }, this); +} + +AsyncWebServer::~AsyncWebServer(){ + reset(); + end(); + if(_catchAllHandler) delete _catchAllHandler; +} + +AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ + _rewrites.add(rewrite); + return *rewrite; +} + +bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ + return _rewrites.remove(rewrite); +} + +AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ + return addRewrite(new AsyncWebRewrite(from, to)); +} + +AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ + _handlers.add(handler); + return *handler; +} + +bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ + return _handlers.remove(handler); +} + +void AsyncWebServer::begin(){ + _server.setNoDelay(true); + _server.begin(); +} + +void AsyncWebServer::end(){ + _server.end(); +} + +#if ASYNC_TCP_SSL_ENABLED +void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ + _server.onSslFileRequest(cb, arg); +} + +void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ + _server.beginSecure(cert, key, password); +} +#endif + +void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ + delete request; +} + +void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ + for(const auto& r: _rewrites){ + if (r->match(request)){ + request->_url = r->toUrl(); + request->_addGetParams(r->params()); + } + } +} + +void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ + for(const auto& h: _handlers){ + if (h->filter(request) && h->canHandle(request)){ + request->setHandler(h); + return; + } + } + + request->addInterestingHeader("ANY"); + request->setHandler(_catchAllHandler); +} + + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + handler->onBody(onBody); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->onRequest(onRequest); + addHandler(handler); + return *handler; +} + +AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ + AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); + addHandler(handler); + return *handler; +} + +void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ + _catchAllHandler->onRequest(fn); +} + +void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ + _catchAllHandler->onUpload(fn); +} + +void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ + _catchAllHandler->onBody(fn); +} + +void AsyncWebServer::reset(){ + _rewrites.free(); + _handlers.free(); + + if (_catchAllHandler != NULL){ + _catchAllHandler->onRequest(NULL); + _catchAllHandler->onUpload(NULL); + _catchAllHandler->onBody(NULL); + } +} + diff --git a/pixelart-controller/lib/ESPAsyncWebServer/src/edit.htm b/pixelart-controller/lib/ESPAsyncWebServer/src/edit.htm new file mode 100644 index 0000000..43d4984 --- /dev/null +++ b/pixelart-controller/lib/ESPAsyncWebServer/src/edit.htm @@ -0,0 +1,627 @@ + + + + +ESP Editor + + + + + + +
+
+
+
+ + + + diff --git a/pixelart-controller/lib/GFX_fonts/Font4x7Fixed.h b/pixelart-controller/lib/GFX_fonts/Font4x7Fixed.h index cfdf82e..e217302 100644 --- a/pixelart-controller/lib/GFX_fonts/Font4x7Fixed.h +++ b/pixelart-controller/lib/GFX_fonts/Font4x7Fixed.h @@ -7,31 +7,31 @@ const uint8_t Font4x7FixedBitmaps[] PROGMEM = { 0xFA, 0xFA, 0xB4, 0x55, 0xF5, 0xF5, 0x50, 0x5F, 0x77, 0xD0, 0x00, 0x94, - 0xA9, 0x48, 0x00, 0x4A, 0xA5, 0xAB, 0x60, 0xD8, 0x00, 0x6A, 0xA4, 0x00, - 0x95, 0x58, 0x00, 0xAA, 0x80, 0x5D, 0x00, 0xD0, 0xE0, 0xF0, 0x25, 0x25, - 0x20, 0x00, 0xF6, 0xDB, 0x7F, 0x00, 0x59, 0x24, 0xB8, 0x00, 0xE4, 0xF9, - 0x38, 0x00, 0xE4, 0xB2, 0x78, 0x00, 0xB6, 0xF2, 0x48, 0x00, 0xF2, 0x72, - 0x78, 0x00, 0xF2, 0x7B, 0x78, 0x00, 0xE4, 0x92, 0x48, 0x00, 0xF6, 0xFB, - 0x78, 0x00, 0xF6, 0xF2, 0x78, 0x00, 0x50, 0x00, 0xDC, 0x00, 0x2A, 0x22, - 0x00, 0xE3, 0x80, 0x88, 0xA8, 0x00, 0x54, 0xA4, 0x10, 0x00, 0x69, 0x9B, - 0xB8, 0x70, 0x56, 0xFB, 0x68, 0x00, 0xD6, 0xFB, 0x70, 0x00, 0x56, 0x49, - 0x50, 0x00, 0xD6, 0xDB, 0x70, 0x00, 0xF2, 0x69, 0x38, 0x00, 0xF2, 0x69, - 0x20, 0x00, 0x69, 0x8B, 0x99, 0x60, 0xB6, 0xFB, 0x68, 0x00, 0xE9, 0x24, - 0xB8, 0x00, 0x24, 0x93, 0x78, 0x00, 0x96, 0xEB, 0x68, 0x00, 0x92, 0x49, - 0x38, 0x00, 0x9F, 0xF9, 0x99, 0x90, 0x99, 0xDF, 0xB9, 0x90, 0x56, 0xDB, - 0x50, 0x00, 0xD6, 0xE9, 0x20, 0x00, 0x56, 0xDB, 0xD8, 0x00, 0xD6, 0xED, - 0x68, 0x00, 0x72, 0x22, 0x70, 0x00, 0xE9, 0x24, 0x90, 0x00, 0xB6, 0xDB, - 0x78, 0x00, 0xB6, 0xDB, 0x50, 0x00, 0x99, 0x99, 0xFF, 0x90, 0xB6, 0xAB, - 0x68, 0x00, 0xB6, 0xA4, 0x90, 0x00, 0xE4, 0xA9, 0x38, 0x00, 0xF2, 0x49, - 0x38, 0x00, 0x91, 0x24, 0x48, 0x00, 0xE4, 0x92, 0x78, 0x00, 0x54, 0x00, - 0xE0, 0x90, 0x65, 0xD6, 0x00, 0x93, 0x5B, 0x70, 0x00, 0x72, 0x46, 0x00, - 0x25, 0xDB, 0x58, 0x00, 0x57, 0xC6, 0x00, 0x69, 0x74, 0x90, 0x00, 0x75, - 0x9E, 0x00, 0x93, 0x5B, 0x68, 0x00, 0xBE, 0x45, 0x5C, 0x00, 0x92, 0xDD, - 0x68, 0x00, 0xFE, 0x00, 0xFF, 0x99, 0x90, 0xD6, 0xDA, 0x00, 0x56, 0xD4, - 0x00, 0xD7, 0x48, 0x00, 0x75, 0x92, 0x00, 0xD6, 0x48, 0x00, 0x71, 0x1C, - 0x00, 0x4B, 0xA4, 0x98, 0x00, 0xB6, 0xDE, 0x00, 0xB6, 0xD4, 0x00, 0x99, - 0x9F, 0x90, 0xB5, 0x5A, 0x00, 0xB5, 0x9C, 0x00, 0xE5, 0x4E, 0x00, 0x29, - 0x44, 0x88, 0x00, 0xFE, 0x00, 0x89, 0x14, 0xA0, 0x00, 0xCC, 0x00 + 0xA9, 0x48, 0x00, 0x4A, 0xA5, 0xAB, 0x60, 0xC0, 0x6A, 0xA4, 0x00, 0x95, + 0x58, 0x00, 0xAA, 0x80, 0x5D, 0x00, 0xC0, 0xE0, 0xF0, 0x25, 0x25, 0x20, + 0x00, 0xF6, 0xDB, 0x7F, 0x00, 0x59, 0x24, 0xB8, 0x00, 0xE4, 0xF9, 0x38, + 0x00, 0xE4, 0xB2, 0x78, 0x00, 0xB6, 0xF2, 0x48, 0x00, 0xF2, 0x72, 0x78, + 0x00, 0xF2, 0x7B, 0x78, 0x00, 0xE4, 0x92, 0x48, 0x00, 0xF6, 0xFB, 0x78, + 0x00, 0xF6, 0xF2, 0x78, 0x00, 0x50, 0x00, 0x4C, 0x00, 0x2A, 0x22, 0x00, + 0xE3, 0x80, 0x88, 0xA8, 0x00, 0x54, 0xA4, 0x10, 0x00, 0x69, 0x9B, 0xB8, + 0x70, 0x56, 0xFB, 0x68, 0x00, 0xD6, 0xFB, 0x70, 0x00, 0x56, 0x49, 0x50, + 0x00, 0xD6, 0xDB, 0x70, 0x00, 0xF2, 0x69, 0x38, 0x00, 0xF2, 0x69, 0x20, + 0x00, 0x69, 0x8B, 0x99, 0x60, 0xB6, 0xFB, 0x68, 0x00, 0xE9, 0x24, 0xB8, + 0x00, 0x24, 0x93, 0x78, 0x00, 0xB6, 0xEB, 0x68, 0x00, 0x92, 0x49, 0x38, + 0x00, 0x9F, 0xF9, 0x99, 0x90, 0x99, 0xDF, 0xB9, 0x90, 0x56, 0xDB, 0x50, + 0x00, 0xD6, 0xE9, 0x20, 0x00, 0x56, 0xDB, 0xD8, 0x00, 0xD6, 0xED, 0x68, + 0x00, 0x72, 0x22, 0x70, 0x00, 0xE9, 0x24, 0x90, 0x00, 0xB6, 0xDB, 0x78, + 0x00, 0xB6, 0xDA, 0x90, 0x00, 0x99, 0x99, 0xFF, 0x90, 0xB6, 0xAB, 0x68, + 0x00, 0xB6, 0xA4, 0x90, 0x00, 0xE4, 0xA9, 0x38, 0x00, 0xF2, 0x49, 0x38, + 0x00, 0x91, 0x24, 0x48, 0x00, 0xE4, 0x92, 0x78, 0x00, 0x54, 0x00, 0xE0, + 0x90, 0x65, 0xD6, 0x00, 0x93, 0x5B, 0x70, 0x00, 0x72, 0x46, 0x00, 0x25, + 0xDB, 0x58, 0x00, 0x57, 0xC6, 0x00, 0x69, 0x74, 0x90, 0x00, 0x75, 0x9E, + 0x00, 0x93, 0x5B, 0x68, 0x00, 0xBE, 0x45, 0x5C, 0x00, 0x92, 0xDD, 0x68, + 0x00, 0xFE, 0x00, 0xFF, 0x99, 0x90, 0xD6, 0xDA, 0x00, 0x56, 0xD4, 0x00, + 0xD7, 0x48, 0x00, 0x75, 0x92, 0x00, 0xD6, 0x48, 0x00, 0x71, 0x1C, 0x00, + 0x4B, 0xA4, 0x98, 0x00, 0xB6, 0xDE, 0x00, 0xB6, 0xD4, 0x00, 0x99, 0x9F, + 0x90, 0xB5, 0x5A, 0x00, 0xB5, 0x9C, 0x00, 0xE5, 0x4E, 0x00, 0x29, 0x44, + 0x88, 0x00, 0xFE, 0x00, 0x89, 0x14, 0xA0, 0x00, 0x78, 0x00 }; const GFXglyph Font4x7FixedGlyphs[] PROGMEM = { @@ -42,96 +42,96 @@ const GFXglyph Font4x7FixedGlyphs[] PROGMEM = { { 7, 3, 7, 4, 0, -7 }, // 0x24 '$' { 11, 3, 7, 4, 0, -7 }, // 0x25 '%' { 15, 4, 7, 5, 0, -7 }, // 0x26 '&' - { 19, 2, 3, 3, 0, -7 }, // 0x27 ''' - { 21, 2, 7, 3, 0, -7 }, // 0x28 '(' - { 24, 2, 7, 3, 0, -7 }, // 0x29 ')' - { 27, 3, 3, 4, 0, -5 }, // 0x2A '*' - { 29, 3, 3, 4, 0, -5 }, // 0x2B '+' - { 31, 2, 2, 3, 0, -2 }, // 0x2C ',' - { 32, 3, 1, 4, 0, -4 }, // 0x2D '-' - { 33, 1, 1, 2, 0, -1 }, // 0x2E '.' - { 34, 3, 7, 4, 0, -7 }, // 0x2F '/' - { 38, 3, 7, 4, 0, -7 }, // 0x30 '0' - { 42, 3, 7, 4, 0, -7 }, // 0x31 '1' - { 46, 3, 7, 4, 0, -7 }, // 0x32 '2' - { 50, 3, 7, 4, 0, -7 }, // 0x33 '3' - { 54, 3, 7, 4, 0, -7 }, // 0x34 '4' - { 58, 3, 7, 4, 0, -7 }, // 0x35 '5' - { 62, 3, 7, 4, 0, -7 }, // 0x36 '6' - { 66, 3, 7, 4, 0, -7 }, // 0x37 '7' - { 70, 3, 7, 4, 0, -7 }, // 0x38 '8' - { 74, 3, 7, 4, 0, -7 }, // 0x39 '9' - { 78, 1, 5, 2, 0, -6 }, // 0x3A ':' - { 80, 1, 6, 2, 0, -6 }, // 0x3B ';' - { 82, 3, 5, 4, 0, -6 }, // 0x3C '<' - { 85, 3, 3, 4, 0, -5 }, // 0x3D '=' - { 87, 3, 5, 4, 0, -6 }, // 0x3E '>' - { 90, 3, 7, 4, 0, -7 }, // 0x3F '?' - { 94, 4, 7, 5, 0, -7 }, // 0x40 '@' - { 98, 3, 7, 4, 0, -7 }, // 0x41 'A' - { 102, 3, 7, 4, 0, -7 }, // 0x42 'B' - { 106, 3, 7, 4, 0, -7 }, // 0x43 'C' - { 110, 3, 7, 4, 0, -7 }, // 0x44 'D' - { 114, 3, 7, 4, 0, -7 }, // 0x45 'E' - { 118, 3, 7, 4, 0, -7 }, // 0x46 'F' - { 122, 4, 7, 5, 0, -7 }, // 0x47 'G' - { 126, 3, 7, 4, 0, -7 }, // 0x48 'H' - { 130, 3, 7, 4, 0, -7 }, // 0x49 'I' - { 134, 3, 7, 4, 0, -7 }, // 0x4A 'J' - { 138, 3, 7, 4, 0, -7 }, // 0x4B 'K' - { 142, 3, 7, 4, 0, -7 }, // 0x4C 'L' - { 146, 4, 7, 5, 0, -7 }, // 0x4D 'M' - { 150, 4, 7, 5, 0, -7 }, // 0x4E 'N' - { 154, 3, 7, 6, 0, -7 }, // 0x4F 'O' - { 158, 3, 7, 4, 0, -7 }, // 0x50 'P' - { 162, 3, 7, 4, 0, -7 }, // 0x51 'Q' - { 166, 3, 7, 4, 0, -7 }, // 0x52 'R' - { 170, 3, 7, 4, 0, -7 }, // 0x53 'S' - { 174, 3, 7, 4, 0, -7 }, // 0x54 'T' - { 178, 3, 7, 4, 0, -7 }, // 0x55 'U' - { 182, 3, 7, 4, 0, -7 }, // 0x56 'V' - { 186, 4, 7, 5, 0, -7 }, // 0x57 'W' - { 190, 3, 7, 4, 0, -7 }, // 0x58 'X' - { 194, 3, 7, 4, 0, -7 }, // 0x59 'Y' - { 198, 3, 7, 4, 0, -7 }, // 0x5A 'Z' - { 202, 3, 7, 4, 0, -7 }, // 0x5B '[' - { 206, 3, 7, 4, 0, -7 }, // 0x5C '\' - { 210, 3, 7, 4, 0, -7 }, // 0x5D ']' - { 214, 3, 3, 4, 0, -7 }, // 0x5E '^' - { 216, 3, 1, 4, 0, -1 }, // 0x5F '_' - { 217, 2, 2, 3, 0, -7 }, // 0x60 '`' - { 218, 3, 5, 4, 0, -5 }, // 0x61 'a' - { 221, 3, 7, 4, 0, -7 }, // 0x62 'b' - { 225, 3, 5, 4, 0, -5 }, // 0x63 'c' - { 228, 3, 7, 4, 0, -7 }, // 0x64 'd' - { 232, 3, 5, 4, 0, -5 }, // 0x65 'e' - { 235, 3, 7, 4, 0, -7 }, // 0x66 'f' - { 239, 3, 5, 4, 0, -5 }, // 0x67 'g' - { 242, 3, 7, 4, 0, -7 }, // 0x68 'h' - { 246, 1, 7, 2, 0, -7 }, // 0x69 'i' - { 247, 2, 7, 3, 0, -7 }, // 0x6A 'j' - { 250, 3, 7, 4, 0, -7 }, // 0x6B 'k' - { 254, 1, 7, 2, 0, -7 }, // 0x6C 'l' - { 256, 4, 5, 5, 0, -5 }, // 0x6D 'm' - { 259, 3, 5, 4, 0, -5 }, // 0x6E 'n' - { 262, 3, 5, 4, 0, -5 }, // 0x6F 'o' - { 265, 3, 5, 4, 0, -5 }, // 0x70 'p' - { 268, 3, 5, 4, 0, -5 }, // 0x71 'q' - { 271, 3, 5, 4, 0, -5 }, // 0x72 'r' - { 274, 3, 5, 4, 0, -5 }, // 0x73 's' - { 277, 3, 7, 4, 0, -7 }, // 0x74 't' - { 281, 3, 5, 4, 0, -5 }, // 0x75 'u' - { 284, 3, 5, 4, 0, -5 }, // 0x76 'v' - { 287, 4, 5, 5, 0, -5 }, // 0x77 'w' - { 290, 3, 5, 4, 0, -5 }, // 0x78 'x' - { 293, 3, 5, 4, 0, -5 }, // 0x79 'y' - { 296, 3, 5, 4, 0, -5 }, // 0x7A 'z' - { 299, 3, 7, 4, 0, -7 }, // 0x7B '{' - { 303, 1, 7, 2, 0, -7 }, // 0x7C '|' - { 305, 3, 7, 4, 0, -7 }, // 0x7D '}' - { 309, 3, 2, 4, 0, -4 } // 0x7E '~' + { 19, 1, 2, 2, 0, -7 }, // 0x27 ''' + { 20, 2, 7, 3, 0, -7 }, // 0x28 '(' + { 23, 2, 7, 3, 0, -7 }, // 0x29 ')' + { 26, 3, 3, 4, 0, -5 }, // 0x2A '*' + { 28, 3, 3, 4, 0, -5 }, // 0x2B '+' + { 30, 1, 2, 2, 0, -2 }, // 0x2C ',' + { 31, 3, 1, 4, 0, -4 }, // 0x2D '-' + { 32, 1, 1, 2, 0, -1 }, // 0x2E '.' + { 33, 3, 7, 4, 0, -7 }, // 0x2F '/' + { 37, 3, 7, 4, 0, -7 }, // 0x30 '0' + { 41, 3, 7, 4, 0, -7 }, // 0x31 '1' + { 45, 3, 7, 4, 0, -7 }, // 0x32 '2' + { 49, 3, 7, 4, 0, -7 }, // 0x33 '3' + { 53, 3, 7, 4, 0, -7 }, // 0x34 '4' + { 57, 3, 7, 4, 0, -7 }, // 0x35 '5' + { 61, 3, 7, 4, 0, -7 }, // 0x36 '6' + { 65, 3, 7, 4, 0, -7 }, // 0x37 '7' + { 69, 3, 7, 4, 0, -7 }, // 0x38 '8' + { 73, 3, 7, 4, 0, -7 }, // 0x39 '9' + { 77, 1, 5, 2, 0, -6 }, // 0x3A ':' + { 79, 1, 6, 2, 0, -6 }, // 0x3B ';' + { 81, 3, 5, 4, 0, -6 }, // 0x3C '<' + { 84, 3, 3, 4, 0, -5 }, // 0x3D '=' + { 86, 3, 5, 4, 0, -6 }, // 0x3E '>' + { 89, 3, 7, 4, 0, -7 }, // 0x3F '?' + { 93, 4, 7, 5, 0, -7 }, // 0x40 '@' + { 97, 3, 7, 4, 0, -7 }, // 0x41 'A' + { 101, 3, 7, 4, 0, -7 }, // 0x42 'B' + { 105, 3, 7, 4, 0, -7 }, // 0x43 'C' + { 109, 3, 7, 4, 0, -7 }, // 0x44 'D' + { 113, 3, 7, 4, 0, -7 }, // 0x45 'E' + { 117, 3, 7, 4, 0, -7 }, // 0x46 'F' + { 121, 4, 7, 5, 0, -7 }, // 0x47 'G' + { 125, 3, 7, 4, 0, -7 }, // 0x48 'H' + { 129, 3, 7, 4, 0, -7 }, // 0x49 'I' + { 133, 3, 7, 4, 0, -7 }, // 0x4A 'J' + { 137, 3, 7, 4, 0, -7 }, // 0x4B 'K' + { 141, 3, 7, 4, 0, -7 }, // 0x4C 'L' + { 145, 4, 7, 5, 0, -7 }, // 0x4D 'M' + { 149, 4, 7, 5, 0, -7 }, // 0x4E 'N' + { 153, 3, 7, 4, 0, -7 }, // 0x4F 'O' + { 157, 3, 7, 4, 0, -7 }, // 0x50 'P' + { 161, 3, 7, 4, 0, -7 }, // 0x51 'Q' + { 165, 3, 7, 4, 0, -7 }, // 0x52 'R' + { 169, 3, 7, 4, 0, -7 }, // 0x53 'S' + { 173, 3, 7, 4, 0, -7 }, // 0x54 'T' + { 177, 3, 7, 4, 0, -7 }, // 0x55 'U' + { 181, 3, 7, 4, 0, -7 }, // 0x56 'V' + { 185, 4, 7, 5, 0, -7 }, // 0x57 'W' + { 189, 3, 7, 4, 0, -7 }, // 0x58 'X' + { 193, 3, 7, 4, 0, -7 }, // 0x59 'Y' + { 197, 3, 7, 4, 0, -7 }, // 0x5A 'Z' + { 201, 3, 7, 4, 0, -7 }, // 0x5B '[' + { 205, 3, 7, 4, 0, -7 }, // 0x5C '\' + { 209, 3, 7, 4, 0, -7 }, // 0x5D ']' + { 213, 3, 3, 4, 0, -7 }, // 0x5E '^' + { 215, 3, 1, 4, 0, -1 }, // 0x5F '_' + { 216, 2, 2, 3, 0, -7 }, // 0x60 '`' + { 217, 3, 5, 4, 0, -5 }, // 0x61 'a' + { 220, 3, 7, 4, 0, -7 }, // 0x62 'b' + { 224, 3, 5, 4, 0, -5 }, // 0x63 'c' + { 227, 3, 7, 4, 0, -7 }, // 0x64 'd' + { 231, 3, 5, 4, 0, -5 }, // 0x65 'e' + { 234, 3, 7, 4, 0, -7 }, // 0x66 'f' + { 238, 3, 5, 4, 0, -5 }, // 0x67 'g' + { 241, 3, 7, 4, 0, -7 }, // 0x68 'h' + { 245, 1, 7, 2, 0, -7 }, // 0x69 'i' + { 246, 2, 7, 3, 0, -7 }, // 0x6A 'j' + { 249, 3, 7, 4, 0, -7 }, // 0x6B 'k' + { 253, 1, 7, 2, 0, -7 }, // 0x6C 'l' + { 255, 4, 5, 5, 0, -5 }, // 0x6D 'm' + { 258, 3, 5, 4, 0, -5 }, // 0x6E 'n' + { 261, 3, 5, 4, 0, -5 }, // 0x6F 'o' + { 264, 3, 5, 4, 0, -5 }, // 0x70 'p' + { 267, 3, 5, 4, 0, -5 }, // 0x71 'q' + { 270, 3, 5, 4, 0, -5 }, // 0x72 'r' + { 273, 3, 5, 4, 0, -5 }, // 0x73 's' + { 276, 3, 7, 4, 0, -7 }, // 0x74 't' + { 280, 3, 5, 4, 0, -5 }, // 0x75 'u' + { 283, 3, 5, 4, 0, -5 }, // 0x76 'v' + { 286, 4, 5, 5, 0, -5 }, // 0x77 'w' + { 289, 3, 5, 4, 0, -5 }, // 0x78 'x' + { 292, 3, 5, 4, 0, -5 }, // 0x79 'y' + { 295, 3, 5, 4, 0, -5 }, // 0x7A 'z' + { 298, 3, 7, 4, 0, -7 }, // 0x7B '{' + { 302, 1, 7, 2, 0, -7 }, // 0x7C '|' + { 304, 3, 7, 4, 0, -7 }, // 0x7D '}' + { 308, 3, 2, 4, 0, -4 } // 0x7E '~' }; const GFXfont Font4x7Fixed PROGMEM = { -(uint8_t *)Font4x7FixedBitmaps, -(GFXglyph *)Font4x7FixedGlyphs, 0x20, 0x7E, 7}; +(uint8_t *)Font4x7FixedBitmaps, +(GFXglyph *)Font4x7FixedGlyphs, 0x20, 0x7E, 7}; diff --git a/pixelart-controller/platformio.ini b/pixelart-controller/platformio.ini index 944c148..998c145 100644 --- a/pixelart-controller/platformio.ini +++ b/pixelart-controller/platformio.ini @@ -18,9 +18,7 @@ framework = arduino lib_deps = adafruit/Adafruit GFX Library@^1.11.5 adafruit/Adafruit BusIO@^1.14.1 - mrfaptastic/ESP32 HUB75 LED MATRIX PANEL DMA Display@^3.0.5 - bblanchon/ArduinoJson@^6.20.0 - me-no-dev/ESP Async WebServer@^1.2.3 + bblanchon/ArduinoJson@^6.21.1 adafruit/RTClib@^2.1.1 fbiego/ESP32Time@^2.0.0 khoih-prog/AsyncHTTPSRequest_Generic@2.5.0 @@ -36,3 +34,30 @@ build_flags = -DARDUINO_ESP32S3_DEV -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 + +[env:n8r8] +platform = espressif32 +board = esp32-s3-n8r8 +framework = arduino +lib_deps = + adafruit/Adafruit GFX Library@^1.11.5 + adafruit/Adafruit BusIO@^1.14.1 + bblanchon/ArduinoJson@^6.21.1 + adafruit/RTClib@^2.1.1 + fbiego/ESP32Time@^2.0.0 + khoih-prog/AsyncHTTPSRequest_Generic@2.5.0 +lib_ignore = + STM32Ethernet + WebServer_ESP32_W6100 + WebServer_WT32_ETH01 + WebServer_ESP32_ENC + WebServer_ESP32_W5500 + ESPAsyncUDP +build_flags = + -DPIXEL_COLOUR_DEPTH_BITS=5 + -DARDUINO_ESP32S3_DEV + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DCONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=130000 + -DCONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP + -DCONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536 diff --git a/pixelart-controller/src/main.cpp b/pixelart-controller/src/main.cpp index dad6778..ff7e6f1 100644 --- a/pixelart-controller/src/main.cpp +++ b/pixelart-controller/src/main.cpp @@ -5,12 +5,14 @@ #include #include #include +#include #include +#include #include #include +#include #include -#include #include #include #include @@ -23,14 +25,11 @@ + /************** ** Globals ** ***************/ -//TODO remove Tests -unsigned long ms_test = 0; -uint32_t lastHeap = 0; - //colors const uint16_t color_twitch = 25108; const uint16_t color_youtube = 0xF800; @@ -40,25 +39,51 @@ const uint16_t color_dark_read = 0xBA00; //bitmaps const uint8_t icon_person[7] = {0x78, 0x48, 0x48, 0x78, 0x00, 0xfc, 0x84}; //6x7 const uint8_t icon_sun[7] = {0x10, 0x44, 0x38, 0xba, 0x38, 0x44, 0x10}; //7x7 +const uint8_t icon_diashow[7] = {0x3e, 0x22, 0xfa, 0x8a, 0x8e, 0x88, 0xf8}; //7x7 +const uint8_t icon_animation[7] = {0x00, 0x72, 0x06, 0xee, 0x06, 0x72, 0x00}; //7x7 const uint8_t icon_star[7] = {0x10, 0x38, 0xfe, 0x7c, 0x38, 0x6c, 0x44}; //7x7 const uint8_t icon_heart[7] = {0x66, 0xff, 0xff, 0xff, 0x7e, 0x3c, 0x18}; //8x7 const uint8_t icon_bell[7] = {0x00, 0x30, 0x78, 0xfc, 0xfc, 0x00, 0x10}; //6x7 const uint8_t icon_eye[14] = {0x1c, 0x00, 0x22, 0x00, 0x49, 0x00, 0x9c, 0x80, 0x49, 0x00, 0x22, 0x00, 0x1c, 0x00}; //9x7 const uint8_t icon_card[128] = {0x00, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xf0, 0x01, 0x92, 0x49, 0x30, 0x01, 0x92, 0x49, 0x30, 0x01, 0x92, 0x49, 0x30, 0x01, 0x92, 0x49, 0x30, 0x01, 0x92, 0x49, 0x30, 0x01, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xe0}; //32x32 +const uint8_t icon_arrow_down[3] = {0xfc, 0x78, 0x30}; //6x3 +const uint8_t icon_arrow_up[3] = {0x30, 0x78, 0xfc}; //6x3 const uint8_t icon_twitch[120] = {0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xf0, 0x30, 0x00, 0x00, 0x30, 0x70, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x18, 0x0c, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00, 0x70, 0xf0, 0x00, 0x00, 0xf0, 0xf0, 0x00, 0x01, 0xe0, 0xff, 0xf1, 0xff, 0xc0, 0xff, 0xf3, 0xff, 0x80, 0xff, 0xf7, 0xff, 0x00, 0xff, 0xff, 0xfe, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}; //28x30 const uint8_t icon_youtube_base[96] = {0x0f, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xf0}; //32x24 const uint8_t icon_youtube_play[24] = {0x80, 0x00, 0xc0, 0x00, 0xf0, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0xff, 0x80, 0xff, 0x80, 0xfe, 0x00, 0xfc, 0x00, 0xf0, 0x00, 0xc0, 0x00, 0x80, 0x00}; //9x12 +const uint16_t icon_instagram[900] = {0x0,0x0,0x0,0x0,0x0,0x0,0x7118,0x7118,0x7118,0x7118,0x7118,0x7118,0x7138,0x7118,0x7118,0x7118,0x7118,0x7118,0x7118,0x7138,0x7118,0x7118,0x7118,0x7118,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7917,0x7917,0x7917,0x7917,0x7917,0x7137,0x7917,0x7937,0x7917,0x7117,0x7917,0x7917,0x7917,0x7137,0x7917,0x7917,0x7917,0x7917,0x7917,0x7917,0x7937,0x7917,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8116,0x7916,0x8116,0x8116,0x8116,0x8116,0x8116,0x8116,0x8116,0x8116,0x8116,0x7916,0x8116,0x8116,0x8116,0x8116,0x8116,0x8116,0x8116,0x8116,0x8117,0x8116,0x8116,0x8116,0x0,0x0,0x0,0x0,0x0,0x8115,0x8115,0x8115,0x8115,0x8115,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8115,0x8115,0x8115,0x8116,0x8115,0x0,0x0,0x0,0x8915,0x88f5,0x8915,0x8915,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x88f5,0x8915,0x88f5,0x8915,0x0,0x0,0x90f4,0x90f4,0x90f4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x90f4,0x90f4,0x90f4,0x0,0x9913,0x98f3,0x98f3,0x98f3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x98f3,0x98f3,0x0,0x0,0x98f3,0x98f3,0x98f3,0x98f3,0xa0f2,0xa0f2,0xa0f2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa0f2,0xa0f2,0xa0f2,0xa0f2,0x0,0x0,0x0,0x0,0x0,0xa0f2,0xa0f2,0x0,0x0,0x0,0xa0f2,0xa0f2,0xa0f2,0xa8f1,0xa8f1,0xa8f1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa8f1,0xa8f1,0xa8f1,0xa8f1,0xa8f1,0xa8f1,0xa8f1,0xa8f1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa8d1,0xa8f1,0xa8f1,0xb0d0,0xb0d0,0xb0d0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xb0d1,0xb0f1,0xb0f1,0xb0d0,0xb0d0,0xb0d0,0xb0d1,0xb0d0,0xb0d1,0xb0d0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xb0d0,0xb0d0,0xb0d0,0xb0d0,0xb0d0,0xb0d0,0x0,0x0,0x0,0x0,0x0,0x0,0xb0d0,0xb0d0,0xb0d0,0xb0d0,0xb0d0,0x0,0x0,0xb8d0,0xb0d0,0xb0d0,0xb0d0,0xb0d0,0x0,0x0,0x0,0x0,0x0,0x0,0xb0d0,0xb0d0,0xb0d0,0xb8cf,0xb8cf,0xb8cf,0x0,0x0,0x0,0x0,0x0,0xb8cf,0xb8cf,0xb8cf,0xb8cf,0x0,0x0,0x0,0x0,0x0,0x0,0xb8cf,0xb8cf,0xb8cf,0xb8ef,0x0,0x0,0x0,0x0,0x0,0xb8cf,0xb8cf,0xb8cf,0xc0ce,0xc0ce,0xc0ce,0x0,0x0,0x0,0x0,0x0,0xc0ce,0xc0ce,0xc0ce,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0ce,0xc0ce,0xc0ce,0x0,0x0,0x0,0x0,0x0,0xc0ce,0xc0ce,0xc0ce,0xc10d,0xc10d,0xc10d,0x0,0x0,0x0,0x0,0xc10e,0xc10e,0xc10d,0xc10d,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc10d,0xc10d,0xc10d,0xc10d,0x0,0x0,0x0,0x0,0xc10d,0xc10d,0xc10d,0xc12d,0xc12d,0xc12d,0x0,0x0,0x0,0x0,0xc12d,0xc12d,0xc12d,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc12d,0xc12d,0xc12d,0x0,0x0,0x0,0x0,0xc12d,0xc12d,0xc12d,0xc16c,0xc16c,0xc16c,0x0,0x0,0x0,0x0,0xc16c,0xc16c,0xc16c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc16c,0xc16c,0xc16c,0x0,0x0,0x0,0x0,0xc16c,0xc16c,0xc16c,0xc18c,0xc18c,0xc18c,0x0,0x0,0x0,0x0,0xc18c,0xc18c,0xc18c,0xc18c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc18c,0xc18c,0xc18c,0xc18c,0x0,0x0,0x0,0x0,0xc18c,0xc18c,0xc18c,0xc1cb,0xc1cb,0xc1cb,0x0,0x0,0x0,0x0,0x0,0xc1cb,0xc1cb,0xc1cb,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc1cb,0xc1cb,0xc1cb,0x0,0x0,0x0,0x0,0x0,0xc1cb,0xc1cb,0xc1cb,0xc9eb,0xc1eb,0xc9eb,0x0,0x0,0x0,0x0,0x0,0xc1eb,0xc1eb,0xc9eb,0xc1eb,0x0,0x0,0x0,0x0,0x0,0x0,0xc1ea,0xc9eb,0xc1eb,0xc9eb,0x0,0x0,0x0,0x0,0x0,0xc1eb,0xc1ea,0xc1eb,0xc22a,0xc20a,0xc22a,0x0,0x0,0x0,0x0,0x0,0x0,0xca0a,0xca2a,0xc20a,0xc22a,0xc22a,0x0,0x0,0xc22a,0xc22a,0xc22a,0xc22a,0xc22a,0x0,0x0,0x0,0x0,0x0,0x0,0xca2a,0xc22a,0xc22a,0xc249,0xc249,0xca4a,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xca4a,0xc249,0xc249,0xca4a,0xc249,0xc249,0xc249,0xc249,0xc229,0xc249,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc249,0xc249,0xc24a,0xca89,0xca89,0xca89,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xca89,0xca89,0xca89,0xca89,0xca89,0xca89,0xca89,0xca89,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xca89,0xca89,0xca89,0xcac9,0xcac9,0xcac9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xcac9,0xcac9,0xcac9,0xcac9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xcac9,0xcac9,0xcac9,0xcb09,0xcb08,0xcb09,0xcb09,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xcb08,0xcb09,0xcb08,0xcb09,0x0,0xd348,0xd348,0xd348,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xd348,0xd348,0xd348,0x0,0x0,0xd3a8,0xd3a8,0xd3a8,0xd3a8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xd3a8,0xd3a8,0xd3a8,0xd3a8,0x0,0x0,0x0,0xd3e8,0xd3e8,0xdbe8,0xd3e8,0xd3e8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xdbe8,0xd3e8,0xd3e8,0xd3e8,0xd3e8,0x0,0x0,0x0,0x0,0x0,0xdc28,0xdc28,0xdc27,0xdc28,0xdc28,0xdc28,0xdc28,0xdc28,0xdc28,0xdc28,0xdc28,0xdc28,0xdc28,0xdc28,0xd428,0xdc28,0xdc28,0xdc28,0xdc28,0xd428,0xdc28,0xdc28,0xdc27,0xdc28,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xdc67,0xdc67,0xdc68,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc67,0xdc88,0xdc67,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xdcc7,0xdcc7,0xdcc7,0xdcc7,0xdcc7,0xdcc7,0xdcc7,0xdcc7,0xdcc7,0xdcc7,0xdcc8,0xdcc7,0xdca7,0xdcc7,0xdcc7,0xdcc7,0xe4a7,0xdcc7,0x0,0x0,0x0,0x0,0x0,0x0}; const uint8_t marks_clock[44][2] = {{0,31},{1,31},{2,31},{3,31},{0,32},{1,32},{2,32},{3,32},{60,31},{61,31},{62,31},{63,31},{60,32},{61,32},{62,32},{63,32},{31,0},{31,1},{31,2},{31,3},{32,0},{32,1},{32,2},{32,3},{31,60},{31,61},{31,62},{31,63},{32,60},{32,61},{32,62},{32,63},{16,5},{47,5},{58,16},{58,47},{5,16},{5,47},{16,58},{47,58},{32,32},{31,31},{32,31},{31,32}}; +struct boot_row { + uint8_t row; + std::vector pixels; + boot_row(uint8_t row, std::vector pixels): + row(row), pixels(pixels) {} +}; +const std::vector boot_sequence = {{59,{31,32}},{58,{30,31,32,33}},{57,{29,30,33,34}},{56,{28,29,34,35}},{55,{28,35}},{54,{27,28,35,36}},{53,{26,27,36,37}},{52,{25,26,37,38}},{51,{24,25,38,39}},{50,{24,39}},{49,{23,24,39,40}},{48,{22,23,40,41}},{47,{21,22,41,42}},{46,{20,21,42,43}},{45,{20,43}},{44,{19,20,43,44}},{43,{18,19,44,45}},{42,{17,18,45,46}},{41,{16,17,46,47}},{40,{16,47}},{39,{15,16,47,48}},{38,{14,15,48,49}},{37,{13,14,49,50}},{36,{12,13,50,51}},{35,{12,51}},{34,{11,12,51,52}},{33,{11,52}},{32,{10,11,52,53}},{31,{10,53}},{30,{10,53}},{29,{10,53}},{28,{10,53}},{27,{10,53}},{26,{10,53}},{25,{10,53}},{24,{10,53}},{23,{9,10,53,54}},{22,{9,10,53,54}},{21,{9,10,53,54}},{20,{9,10,53,54}},{19,{9,10,53,54}},{18,{9,54}},{17,{9,54}},{16,{9,54}},{15,{9,54}},{14,{9,54}},{13,{9,54}},{12,{9,54}},{11,{9,54}},{10,{9,54}},{9,{8,9,54,55}},{8,{8,9,54,55}},{7,{8,9,54,55}},{6,{8,9,54,55}},{5,{8,9,54,55}},{4,{8,9,54,55}},{3,{8,9,54,55}},{4,{10,53}},{5,{10,11,52,53}},{6,{10,11,12,51,52,53}},{7,{10,12,13,50,51,53}},{8,{10,13,14,49,50,53}},{9,{10,11,14,15,48,49,52,53}},{10,{11,15,16,47,48,52}},{11,{11,16,17,46,47,52}},{12,{11,12,17,18,45,46,51,52}},{13,{12,18,19,44,45,51}},{14,{12,13,19,20,43,44,50,51}},{15,{13,20,21,42,43,50}},{16,{13,21,22,41,42,50}},{17,{13,14,22,23,40,41,49,50}},{18,{14,23,24,39,40,49}},{19,{14,15,24,25,38,39,48,49}},{19, {26,27,28,35,36,37}}, {18, {29,30,31,32,33,34}},{20,{15,23,24,39,40,48}},{21,{15,22,23,40,41,48}},{22,{15,16,21,22,41,42,47,48}},{23,{16,20,21,42,43,47}},{24,{16,17,19,20,43,44,46,47}},{25,{17,18,19,44,45,46}},{26,{17,18,45,46}},{27,{16,17,18,45,46,47}},{28,{15,16,18,19,44,45,47,48}},{29,{14,15,19,44,48,49}},{30,{13,14,19,20,43,44,49,50}},{31,{12,13,20,43,50,51}},{32,{12,13,20,43,50,51}},{33,{12,13,14,15,16,20,21,42,43,47,48,49,50,51}},{34,{16,17,21,42,46,47}},{35,{17,18,19,21,22,41,42,44,45,46}},{36,{19,20,22,41,43,44}},{37,{20,21,22,41,42,43}},{38,{21,22,23,40,41,42}},{39,{22,23,40,41}},{40,{23,24,39,40}},{41,{24,39}},{42,{24,25,38,39}},{43,{25,38}},{44,{25,26,37,38}},{45,{26,37}},{46,{26,37}},{47,{26,27,36,37}},{48,{27,36}},{49,{27,28,35,36}},{50,{28,35}},{51,{28,35}},{52,{28,29,34,35}},{53,{28,29,34,35}},{54,{29,34}},{55,{29,30,33,34}},{56,{30,33}},{57,{31,32}}}; + //slopes for mapping ranges to pixel width //? (output_end - output_start) / (input_end - input_start) +//? 44 / input_range const double slope_brightness = .189; -const double slope_diashow = 0; -const double slope_animation = 0; - -//structs +const double slope_animation = .09166; +const double slope_diashow = .0007457; + +//paths +const char* update_file = "/firmware.bin"; +const char* server_home_dir = "/webinterface"; +const char* server_home_file = "/webinterface/index.html"; +const char* server_version_file = "/webinterface/version.json"; +const char* upload_directory = "/tmp"; +const char* images_folder = "/images"; + +//webpages +const char* not_found = "DerEffi's Pixelart

404

Looks like the page you were looking for is no longer here.

"; +const char* missing_sd = "DerEffi's Pixelart

No SD Card.

"; + +//enums enum overlay_type { OVERLAY_NONE, OVERLAY_BRIGHTNESS, @@ -82,6 +107,17 @@ enum clock_type { CLOCK_DIGITAL_BIG }; +enum menu_mode { + MENU_NONE, + MENU_OVERVIEW, + MENU_CLOCK, + MENU_DATETIME, + MENU_WIFI, + MENU_WIFI_CONNECT, + MENU_WIFI_HOST +}; + +//data interfaces struct socials_channel { char* type; char* name; @@ -92,54 +128,149 @@ struct socials_channel { }; struct image_meta { - char* image; - char* animation; + char* folder; + uint16_t prefix; bool animated; - image_meta(char* image, bool animated = false, char* animation = nullptr): - image(image), animation(animation), animated(animated) {} + image_meta(char* folder, uint16_t prefix = 0, bool animated = false): + folder(folder), prefix(prefix), animated(animated) {} +}; + +struct pixel_data { + uint8_t x; + uint8_t y; + uint16_t color; + pixel_data(uint8_t x, uint8_t y, uint16_t color): + x(x), y(y), color(color) {} +}; + +struct available_network { + char * ssid; + int32_t rssi; + int32_t encryption; + available_network(char* ssid, int32_t rssi, int32_t encryption): + ssid(ssid), rssi(rssi), encryption(encryption) {} +}; + +struct file_operation { + char * src; + char * dst; + file_operation(char* src, char* dst): + src(src), dst(dst) {} + file_operation(char* src): + src(src), dst(strdup("")) {} }; -//settings +//Interrupt flags +bool rot1_a_flag = false; +bool rot1_b_flag = false; +bool rot2_a_flag = false; +bool rot2_b_flag = false; +bool rot3_a_flag = false; +bool rot3_b_flag = false; + +bool btn1_pressed = false; +bool btn2_pressed = false; +unsigned long ms_btn3_pressed = 0; +bool btn3_released = false; +bool rot1_pressed = false; +bool rot2_pressed = false; +bool rot3_pressed = false; +int rot1_clicks = 0; +int rot2_clicks = 0; +int rot3_clicks = 0; + +//boot sequence +uint8_t loading_step = 0; +unsigned long ms_loading = 0; +bool loading_cycle = true; + +//general state Preferences preferences; -bool booted = false; unsigned long ms_current = 0; -bool requested_restart = false; +unsigned long ms_requested_restart = 0; overlay_type overlay = OVERLAY_NONE; char* overlay_text = strdup(""); unsigned long ms_overlay = 0; + +menu_mode menu = MENU_NONE; +uint8_t menu_selection = 0; + +boolean menu_time_changed = false; +uint8_t menu_day = 0; +uint8_t menu_month = 0; +uint8_t menu_year = 0; +uint8_t menu_hour = 0; +uint8_t menu_minute = 0; +uint8_t menu_second = 0; + uint8_t brightness = 128; +bool animation_enabled = false; +bool diashow_enabled = false; +bool diashow_modes = false; +uint16_t animation_time = 100; +unsigned long ms_animation = 0; +uint32_t diashow_time = 5000; +unsigned long ms_diashow = 0; + +//Display +MatrixPanel_I2S_DMA *panel = nullptr; +display_mode current_mode = MODE_IMAGES; +bool display_change = false; + +SPIClass *spi = NULL; +std::vector image_index; +uint16_t image_prefix_max = 0; +uint16_t selected_image = 0; +uint16_t current_image[64][64] = {}; +std::vector> animation; +bool image_loaded = false; +uint16_t animation_frame = 0; + +//RTC +RTC_DS3231 rtc_ext; +ESP32Time rtc_int(0); +bool rtc_ext_enabled = false; +char* ntp_server = strdup(TIME_SERVER); +char* timezone = strdup(TIME_ZONE); +bool update_time = TIME_UPDATE_AUTO; +bool time_format24 = TIME_FORMAT24; +bool rtc_ext_adjust = false; +unsigned long ms_rtc_ext_adjust = 0; +//Clock +unsigned long ms_clock = 0; +clock_type current_clock_mode = CLOCK_ANALOG; +bool clock_seconds = true; +bool clock_blink = false; +bool clock_year = true; //Wifi bool wifi_connect = WIFI_CONNECT_DEFAULT; bool wifi_host = WIFI_HOST_DEFAULT; bool wifi_setup_complete = true; +unsigned long ms_wifi_scan_requested = 0; +unsigned long ms_wifi_scan_last = 0; +bool wifi_scan_pending = false; +std::vector available_networks; char* wifi_ssid = strdup(WIFI_SSID_DEFAULT); char* wifi_ap_ssid = strdup(WIFI_AP_SSID_DEFAULT); char* wifi_password = strdup(WIFI_PASSWORD_DEFAULT); char* wifi_ap_password = strdup(WIFI_AP_PASSWORD_DEFAULT); unsigned long ms_wifi_routine = 0; unsigned long ms_wifi_reconnect = 0; -AsyncDNSServer dnsServer; - +unsigned long ms_wifi_restart = 0; +DNSServer dns_server; //Server AsyncWebServer server(80); +bool server_setup_complete = false; char* api_key = strdup(""); unsigned long ms_api_key_request = 0; unsigned long ms_api_key_approve = 0; - - -//LED Panel -MatrixPanel_I2S_DMA *panel = nullptr; -display_mode current_mode = MODE_IMAGES; -bool display_change = false; - -std::vector image_index; -uint16_t selected_image = 0; -uint16_t current_image[64][64] = {}; -bool image_loaded = false; +File uploading_file; +bool upload_success = true; +std::vector file_operations; //Socials AsyncHTTPSRequest http_socials; @@ -151,44 +282,6 @@ char* socials_request = strdup(SOCIALS_REQUEST); int socials_channel_current = 0; std::vector socials_channels; -//Clock -unsigned long ms_clock = 0; -clock_type current_clock_mode = CLOCK_ANALOG; -bool clock_seconds = true; -bool clock_blink = false; -bool clock_year = true; - -//Interrupt flags -bool rot1_a_flag = false; -bool rot1_b_flag = false; -bool rot2_a_flag = false; -bool rot2_b_flag = false; -bool rot3_a_flag = false; -bool rot3_b_flag = false; - -bool btn1_pressed = false; -bool btn2_pressed = false; -unsigned long ms_btn3_pressed = 0; -bool btn3_released = false; -bool rot1_pressed = false; -bool rot2_pressed = false; -bool rot3_pressed = false; -int rot1_clicks = 0; -int rot2_clicks = 0; -int rot3_clicks = 0; - - -//RTC -RTC_DS3231 rtc_ext; -ESP32Time rtc_int(0); -bool rtc_ext_enabled = false; -char* ntp_server = strdup(TIME_SERVER); -char* timezone = strdup(TIME_ZONE); -bool update_time = TIME_UPDATE_AUTO; -bool time_format24 = TIME_FORMAT24; -bool rtc_ext_adjust = false; -unsigned long ms_rtc_ext_adjust = 0; - @@ -203,30 +296,36 @@ void on_socials_response(){ if(!error && socials_response_doc.is()) { JsonArray socials_response_array = socials_response_doc.as(); - //clear old data - for(int i = 0; i < socials_channels.size(); i++) { - free(socials_channels[i].follower); - free(socials_channels[i].views); - free(socials_channels[i].type); - free(socials_channels[i].name); - } - socials_channels.clear(); - - //fill with new data - for(int i = 0; i < socials_response_array.size(); i++) { - const char* t = socials_response_array[i]["t"].as(); - const char* d = socials_response_array[i]["d"].as(); - const char* c = socials_response_array[i]["c"].as(); - const char* f = socials_response_array[i]["f"].as(); - const char* v = socials_response_array[i]["v"].as(); - - socials_channel channel = { - t ? strdup(t) : strdup(""), - d ? strdup(d) : c ? strdup(c) : strdup(""), - f ? strdup(f) : strdup("0"), - v ? strdup(v) : strdup("0") - }; - socials_channels.emplace_back(channel); + //if error or empty data preserve last state + if(socials_response_array.size() > 0) { + + //clear old data + for(int i = 0; i < socials_channels.size(); i++) { + free(socials_channels[i].follower); + free(socials_channels[i].views); + free(socials_channels[i].type); + free(socials_channels[i].name); + } + socials_channels.clear(); + + //fill with new data + for(int i = 0; i < socials_response_array.size(); i++) { + const char* t = socials_response_array[i]["t"].as(); + const char* d = socials_response_array[i]["d"].as(); + const char* c = socials_response_array[i]["c"].as(); + const char* f = socials_response_array[i]["f"].as(); + const char* v = socials_response_array[i]["v"].as(); + + if(d || c) { + socials_channel channel = { + t ? strdup(t) : strdup(""), + d ? strdup(d) : c ? strdup(c) : strdup(""), + f ? strdup(f) : strdup("0"), + v ? strdup(v) : strdup("0") + }; + socials_channels.emplace_back(channel); + } + } } display_change = true; @@ -263,17 +362,14 @@ void socials_refresh() { ** Misc ** ************/ void rtc_internal_adjust() { - DateTime time = rtc_ext.now(); - rtc_int.setTime(time.second(), time.minute(), time.hour(), time.day(), time.month(), time.year()); + if(rtc_ext_enabled) { + rtc_int.setTime(rtc_ext.now().unixtime()); + } } void rtc_external_adjust() { if(rtc_ext_enabled) { - struct tm timeinfo; - if(getLocalTime(&timeinfo, 500)) - { - rtc_ext.adjust(DateTime(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec)); - } + rtc_ext.adjust(DateTime(rtc_int.getEpoch())); } rtc_ext_adjust = false; ms_rtc_ext_adjust = 0; @@ -288,7 +384,8 @@ void wifi_on_connected() { } } - socials_refresh(); + display_change = true; + ms_socials_request = millis() + 100; } char* generate_uid(){ @@ -307,6 +404,51 @@ bool verify_api_key(AsyncWebServerRequest * request) { return request->hasHeader("apiKey") && request->getHeader("apiKey")->value().equals(api_key); } +void menu_copy_time() { + menu_year = rtc_int.getYear() % 100; + menu_month = rtc_int.getMonth() + 1; + menu_day = rtc_int.getDay(); + menu_hour = rtc_int.getHour(true); + menu_minute = rtc_int.getMinute(); + menu_second = rtc_int.getSecond(); +} + +void menu_correct_date() { + if(menu_day > 30 && (menu_month == 4 || menu_month == 6 || menu_month == 9 || menu_month == 11)) + menu_day = rot3_clicks > 0 ? 1 : 30; + + if(menu_day > 29 && menu_month == 2 && menu_year % 4 == 0) + menu_day = rot3_clicks > 0 ? 1 : 29; + + if(menu_day > 28 && menu_month == 2 && menu_year % 4 != 0) + menu_day = rot3_clicks > 0 ? 1 : 28; +} + +void menu_update_time() { + struct tm t = {0}; + t.tm_year = menu_year + 100; + t.tm_mon = menu_month - 1; + t.tm_mday = menu_day; + t.tm_hour = menu_hour; + t.tm_min = menu_minute; + t.tm_sec = menu_second; + rtc_int.setTime(mktime(&t)); + + if(rtc_ext_enabled) { + ms_rtc_ext_adjust = millis() + 1000; + rtc_ext_adjust = true; + } + + if(wifi_connect) { + preferences.begin(PREFERENCES_NAMESPACE); + update_time = false; + preferences.putBool("update_time", update_time); + preferences.end(); + } + + menu_time_changed = false; +} + @@ -315,7 +457,73 @@ bool verify_api_key(AsyncWebServerRequest * request) { *************/ bool compare_image_name(image_meta i1, image_meta i2) { - return strcmp(i1.image, i2.image) < 0; + return strcmp(i1.folder, i2.folder) < 0; +} + +char* get_parent_folder(const char* folder) { + std::string folder_str(folder); + size_t found = folder_str.find_last_of("/\\"); + return found > 0 ? strdup(folder_str.substr(0, found).c_str()) : strdup("/"); +} + +bool remove_recursive(fs::FS &fs, File file) { + if(file) { + char path[strlen(file.path()) + 1]; + strcpy(path, file.path()); + + if(file.isDirectory()) { + + File nested_file = file.openNextFile(); + while(nested_file) { + if(!remove_recursive(fs, nested_file)) { + return false; + } + nested_file = file.openNextFile(); + } + + file.close(); + return fs.rmdir(path); + } else { + file.close(); + return fs.remove(path); + } + } + + return false; +} + +bool remove_recursive(fs::FS &fs, const char * path) { + if(SD.exists(path)) { + return remove_recursive(fs, fs.open(path)); + } + + return false; +} + +bool ensure_folder(fs::FS &fs, const char * path) { + if(SD.exists(path)) { + File path_object = SD.open(path); + if(path_object) { + bool isDirectory = path_object.isDirectory(); + path_object.close(); + if(!isDirectory) { + if(SD.remove(path)) { + return SD.mkdir(path); + } + } else { + return true; + } + } + } else { + char * parent = get_parent_folder(path); + if(strlen(parent) > 1) { + ensure_folder(fs, parent); + } + free(parent); + return SD.mkdir(path); + } + + return false; } //reindex images folder of sd card for loading and diplaying byte data @@ -323,33 +531,67 @@ void sd_index() { //remove old data from index for(int i = 0; i < image_index.size(); i++) { - free(image_index[i].image); - free(image_index[i].animation); + free(image_index[i].folder); } image_index.clear(); + image_prefix_max = 0; //fill vector with new data - if(SD.exists("/images")) { - File folder = SD.open("/images"); + if(SD.exists(images_folder)) { + File folder = SD.open(images_folder); if(folder.isDirectory()) { char file_path[255]; File image = folder.openNextFile(); while(image) { + //check for image file if(image.isDirectory()) { - const char* path = image.path(); - strcpy(file_path, path); + image_meta meta = { + strdup(image.path()) + }; + strcpy(file_path, meta.folder); strcat(file_path, "/image.pxart"); if(SD.exists(file_path) && !SD.open(file_path).isDirectory()) { - image_meta meta = { - strdup(file_path) - }; - - strcpy(file_path, path); + //rename folder if no prefix number is given + const char* name = image.name(); + bool rename = false; + for(int i = 0; i < 3; i++) { + if(name[i] < 48 || name[i] > 57) { + rename = true; + break; + } + } + if(name[3] != 32 || name[4] != 45 || name[5] != 32) + rename = true; + + char prefix[4]; + if(rename) { + do { + meta.prefix++; + sprintf(prefix, "%03d", meta.prefix); + strcpy(file_path, images_folder); + strcat(file_path, "/"); + strcat(file_path, prefix); + strcat(file_path, " - "); + strcat(file_path, name); + } while(SD.exists(file_path)); + if(SD.rename(meta.folder, file_path)) { + free(meta.folder); + meta.folder = strdup(file_path); + } + } else { + strncpy(prefix, name, 3); + sscanf(prefix, "%d", &meta.prefix); + } + + if(meta.prefix > image_prefix_max) + image_prefix_max = meta.prefix; + + //check for animation file + strcpy(file_path, meta.folder); strcat(file_path, "/animation.pxart"); if(SD.exists(file_path) && !SD.open(file_path).isDirectory()) { meta.animated = true; - meta.animation = strdup(file_path); } image_index.emplace_back(meta); @@ -369,47 +611,101 @@ bool sd_connected() { return true; SD.end(); - if(!SD.begin(GPIO_SD_CS, SPI)) + if(!SD.begin(GPIO_SD_CS, *spi)) return false; bool success = SD.exists("/"); - if(success) + if(success) { + //setup default directories + remove_recursive(SD, upload_directory); + ensure_folder(SD, images_folder); + ensure_folder(SD, server_home_dir); + sd_index(); + } return success; } bool sd_load_image(image_meta image) { - if(SD.exists(image.image)) { - File file = SD.open(image.image); + animation.clear(); + char file_path[255]; + strcpy(file_path, image.folder); + strcat(file_path, "/image.pxart"); - int x = 0; - int y = 0; - int position = 0; + if(SD.exists(file_path)) { + File file = SD.open(file_path); + + uint8_t x = 0; + uint8_t y = 0; + uint position = 0; int length = file.available(); - const static int buffersize = 512; - char color_data[buffersize] = {}; + const static uint16_t buffersize = 512; + char buffer_data[buffersize] = {}; //read in 4 pixelmatrix lines because of array size limit while(length > position) { file.seek(position); - file.readBytes(color_data, buffersize); + file.readBytes(buffer_data, buffersize); for(int i = 0; i < buffersize; i += 2) { - current_image[y][x] = (color_data[i+1] << 8) + color_data[i]; + current_image[y][x] = (buffer_data[i+1] << 8) + buffer_data[i]; x++; if(x >= 64) { x = 0; y++; } if(y >= 64) - return true; + break; } + if(y >= 64) + break; position += buffersize; } - //TODO animation + if(image.animated) { + strcpy(file_path, image.folder); + strcat(file_path, "/animation.pxart"); + + if(SD.exists(file_path)) { + File file = SD.open(file_path); + + file.seek(0); + file.readBytes(buffer_data, 4); + std::vector frame; + + position = 4; + while(length > position) { + uint fragment_size = length - position - 1; + if(fragment_size > buffersize) + fragment_size = buffersize; + file.seek(position); + file.readBytes(buffer_data, fragment_size); + + for(int i = 0; i < fragment_size - 3; i += 4) { + x = buffer_data[i]; + y = buffer_data[i+1]; + if(x >= 64 || y >= 64) { + animation.emplace_back(frame); + frame.clear(); + } else { + pixel_data pixel = { + x, + y, + (uint16_t)((buffer_data[i+3] << 8) + buffer_data[i+2]) + }; + frame.emplace_back(pixel); + } + } + + position += buffersize; + } + + animation.emplace_back(frame); + frame.clear(); + } + } return true; } @@ -417,6 +713,88 @@ bool sd_load_image(image_meta image) { return false; } +void restart() { + panel->clearScreen(); + if(PANEL_DOUBLE_BUFFER) + panel->flipDMABuffer(); + + panel->stopDMAoutput(); + if(sd_connected()) + SD.end(); + + ESP.restart(); +} + +void firmware_update() { + if(sd_connected() && SD.exists(update_file)) { + //Display update process + panel->clearScreen(); + panel->setTextSize(1); + panel->setTextWrap(false); + panel->setBrightness(192); + panel->setCursor(5, 12); + panel->write("Updating Device"); + panel->setCursor(7, 28); + panel->write("Do NOT remove"); + panel->setCursor(12, 37); + panel->write("the SD Card"); + panel->setCursor(10, 46); + panel->write("or power off"); + panel->setCursor(13, 55); + panel->write("this device"); + + if(PANEL_DOUBLE_BUFFER) + panel->flipDMABuffer(); + + //Update + File firmware = SD.open(update_file); + if(!firmware.isDirectory()) { + size_t update_size = firmware.size(); + if(update_size > 524288 && Update.begin(update_size)) { + size_t written_size = Update.writeStream(firmware); + if((written_size != update_size || !Update.end() || !Update.isFinished()) && Update.canRollBack()) + Update.rollBack(); + } + } + + //clean up + firmware.close(); + SD.remove(update_file); + restart(); + } +} + +void sd_operate_files() { + int count = file_operations.size(); + if(count > 0) { + + if(sd_connected()) { + for(int i = 0; i < count; i++) { + if(strlen(file_operations[i].src) >= 3 && SD.exists(file_operations[i].src)) { + if(strlen(file_operations[i].dst) >= 3) { + remove_recursive(SD, file_operations[i].dst); + + char * dst_folder = get_parent_folder(file_operations[i].dst); + ensure_folder(SD, dst_folder); + free(dst_folder); + + SD.rename(file_operations[i].src, file_operations[i].dst); + } else { + remove_recursive(SD, file_operations[i].src); + } + } + + free(file_operations[i].src); + free(file_operations[i].dst); + } + + sd_index(); + } + + file_operations.erase(file_operations.begin(), file_operations.begin() + count); + } +} + @@ -442,9 +820,25 @@ void display_overlay() { //Sun icon panel->drawBitmap(54, 2, icon_sun, 7, 7, 0xFFFF); } else if(overlay == OVERLAY_ANIMATION_SPEED) { + //Progress Bar + panel->drawRect(2, 2, 49, 7, 0xFFFF); + //maps animation_time range to progress bar width (max: 45px) + //? floor(output_start + (slope * (input - input_start)) + 0.5) + panel->fillRect(4, 4, floor((45 - slope_animation * (animation_time - 20)) + .5), 3, 0xFFFF); + + //Sun icon + panel->drawBitmap(54, 2, icon_animation, 7, 7, 0xFFFF); } else if(overlay == OVERLAY_DIASHOW_SPEED) { + //Progress Bar + panel->drawRect(2, 2, 49, 7, 0xFFFF); + //maps diashow_time range to progress bar width (max: 45px) + //? floor(output_start + (slope * (input - input_start)) + 0.5) + panel->fillRect(4, 4, floor((45 - slope_diashow * (diashow_time - 1000)) + .5), 3, 0xFFFF); + + //Sun icon + panel->drawBitmap(54, 2, icon_diashow, 7, 7, 0xFFFF); } else if(overlay == OVERLAY_TEXT) { int16_t x1, y1; uint16_t width, height; @@ -458,15 +852,13 @@ void display_overlay() { } } -void display_overlay(overlay_type type, char* text = strdup("")) { +void display_overlay(overlay_type type, const char* text = "") { overlay = type; - if(overlay != OVERLAY_TEXT) - free(text); switch(overlay) { case OVERLAY_TEXT: free(overlay_text); - overlay_text = text; + overlay_text = strdup(text); break; case OVERLAY_BRIGHTNESS: panel->setPanelBrightness(brightness); @@ -477,28 +869,267 @@ void display_overlay(overlay_type type, char* text = strdup("")) { display_change = true; } -// Display static images on led matrix by pixelarray -void display_loaded_image() { - for(int y = 0; y < 64; y++) { - for(int x = 0; x < 64; x++) { - panel->drawPixel(x, y, current_image[y][x]); - } - } - - display_overlay(); - - if(PANEL_DOUBLE_BUFFER) - panel->flipDMABuffer(); -} - -void display_card_missing() { +void display_menu() { //transparent background panel->clearScreen(); - //social icon - panel->drawBitmap(15, 15, icon_card, 32, 32, color_dark_read); + panel->setTextSize(1); + char * headline = nullptr; + + char clockText[10]; + int year = rtc_int.getYear() % 100; + int month = rtc_int.getMonth() + 1; + int day = rtc_int.getDay(); + int hour = rtc_int.getHour(true); + int minute = rtc_int.getMinute(); + int second = rtc_int.getSecond(); + int arrowOffset = menu_selection == 0 ? 13 : 36; - display_overlay(); + int selY = 0; + switch(menu_selection) { + case 0: + selY = 12; + break; + case 1: + selY = 23; + break; + case 2: + selY = 34; + break; + case 3: + selY = 45; + break; + } + + switch(menu) { + case MENU_OVERVIEW: + headline = strdup("Settings"); + + //Draw menu points + panel->setCursor(3, 21); + panel->write("Clock Settings"); + panel->setCursor(3, 32); + panel->write("Set Date/Time"); + panel->setCursor(3, 43); + panel->write("Wifi Settings"); + + //Draw selection box + panel->drawRect(1, selY, 62, 11, 0xFFFF); + break; + case MENU_CLOCK: + headline = strdup("Clock Settings"); + + //Draw menu points + panel->setCursor(3, 21); + panel->write("Show Seconds"); + panel->setCursor(3, 32); + panel->write("Show Year"); + panel->setCursor(3, 43); + panel->write("Show Blink"); + panel->setCursor(3, 54); + panel->write("24h Format"); + + //draw checkboxes + if(clock_seconds) + panel->fillRect(55, 15, 5, 5, 0xFFFF); + else + panel->drawRect(55, 15, 5, 5, 0xFFFF); + + if(clock_year) + panel->fillRect(55, 26, 5, 5, 0xFFFF); + else + panel->drawRect(55, 26, 5, 5, 0xFFFF); + + if(clock_blink) + panel->fillRect(55, 37, 5, 5, 0xFFFF); + else + panel->drawRect(55, 37, 5, 5, 0xFFFF); + + if(time_format24) + panel->fillRect(55, 48, 5, 5, 0xFFFF); + else + panel->drawRect(55, 48, 5, 5, 0xFFFF); + + //Draw selection box + panel->drawRect(1, selY, 62, 11, 0xFFFF); + break; + case MENU_DATETIME: + headline = strdup("Set Date/Time"); + + panel->setTextSize(2); + + //time + if(menu_time_changed) { + year = menu_year; + month = menu_month; + day = menu_day; + hour = menu_hour; + minute = menu_minute; + second = menu_second; + } + sprintf(clockText, "%02d.%02d.%02d", day, month, year); + panel->setCursor(5, 32); + panel->write(clockText); + + sprintf(clockText, "%02d:%02d:%02d", hour, minute, second); + panel->setCursor(5, 55); + panel->write(clockText); + + //arrows + panel->drawBitmap(9, arrowOffset, icon_arrow_up, 6, 3, 0xFFFF); + panel->drawBitmap(29, arrowOffset, icon_arrow_up, 6, 3, 0xFFFF); + panel->drawBitmap(49, arrowOffset, icon_arrow_up, 6, 3, 0xFFFF); + + panel->drawBitmap(9, arrowOffset + 21, icon_arrow_down, 6, 3, 0xFFFF); + panel->drawBitmap(29, arrowOffset + 21, icon_arrow_down, 6, 3, 0xFFFF); + panel->drawBitmap(49, arrowOffset + 21, icon_arrow_down, 6, 3, 0xFFFF); + + panel->setTextSize(1); + + break; + case MENU_WIFI: + headline = strdup("Wifi Settings"); + + //Draw menu points + panel->setCursor(3, 21); + panel->write("Connect"); + panel->setCursor(3, 32); + panel->write("Connection Stats"); + panel->setCursor(3, 43); + panel->write("Host"); + panel->setCursor(3, 54); + panel->write("Host Stats"); + + //draw checkboxes + if(wifi_connect) + panel->fillRect(55, 15, 5, 5, 0xFFFF); + else + panel->drawRect(55, 15, 5, 5, 0xFFFF); + + if(wifi_host) + panel->fillRect(55, 37, 5, 5, 0xFFFF); + else + panel->drawRect(55, 37, 5, 5, 0xFFFF); + + //Draw selection box + panel->drawRect(1, selY, 62, 11, 0xFFFF); + break; + case MENU_WIFI_CONNECT: + headline = strdup("Wifi Connection"); + + //Draw menu points + panel->setCursor(3, 21); + panel->write(WiFi.localIP().toString().c_str()); + panel->setCursor(3, 32); + panel->write(wifi_ssid); + panel->setCursor(3, 43); + panel->write("Connected"); + if(wifi_connect && wifi_setup_complete) + panel->fillRect(55, 37, 5, 5, 0xFFFF); + else + panel->drawRect(55, 37, 5, 5, 0xFFFF); + + break; + case MENU_WIFI_HOST: + headline = strdup("Wifi Hosting"); + + //Draw menu points + panel->setCursor(3, 21); + panel->write(WiFi.softAPIP().toString().c_str()); + panel->setCursor(3, 32); + panel->write(wifi_ap_ssid); + panel->setCursor(3, 43); + panel->setTextWrap(true); + panel->write(wifi_ap_password); + panel->setTextWrap(false); + + break; + } + + //display headline + if(headline) { + int16_t x1, y1; + uint16_t width, height; + panel->setTextColor(0xFFFF); + + panel->getTextBounds(headline, 0, 2, &x1, &y1, &width, &height); + panel->setCursor(64 > width ? .5 * (64 - width) : 0, 9); + panel->write(headline); + free(headline); + } + + display_overlay(); + + if(PANEL_DOUBLE_BUFFER) + panel->flipDMABuffer(); +} + +//Display loading animation +void display_loading() { + if(ms_loading == 0) + ms_loading = millis() + 30; + + if(loading_step == 0 && loading_cycle) { + panel->clearScreen(); + if(PANEL_DOUBLE_BUFFER) { + panel->flipDMABuffer(); + panel->clearScreen(); + } + } + + for(int pixel = 0; pixel < boot_sequence[loading_step].pixels.size(); pixel++) { + panel->drawPixel(boot_sequence[loading_step].pixels[pixel], boot_sequence[loading_step].row, loading_cycle ? 0xFFFF : 0); + } + + if(PANEL_DOUBLE_BUFFER) { + panel->flipDMABuffer(); + + for(int pixel = 0; pixel < boot_sequence[loading_step].pixels.size(); pixel++) { + panel->drawPixel(boot_sequence[loading_step].pixels[pixel], boot_sequence[loading_step].row, loading_cycle ? 0xFFFF : 0); + } + } +} + +void reset_loading() { + ms_loading = 0; + loading_step = 0; + loading_cycle = true; +} + +// Display static images on led matrix by pixelarray +void display_loaded_image() { + for(int y = 0; y < 64; y++) { + for(int x = 0; x < 64; x++) { + panel->drawPixel(x, y, current_image[y][x]); + } + } + + display_overlay(); + + if(PANEL_DOUBLE_BUFFER) + panel->flipDMABuffer(); +} + +void display_next_frame() { + if(image_index[selected_image].animated && animation.size() > 0) { + animation_frame++; + if(animation_frame >= animation.size()) + animation_frame = 0; + std::vector changes = animation[animation_frame]; + for(int i = 0; i < changes.size(); i++) { + current_image[changes[i].y][changes[i].x] = changes[i].color; + } + } +} + +void display_card_missing() { + //transparent background + panel->clearScreen(); + + //social icon + panel->drawBitmap(15, 15, icon_card, 32, 32, color_dark_read); + + display_overlay(); //print on screen if(PANEL_DOUBLE_BUFFER) @@ -513,10 +1144,18 @@ void display_social_channel(char* type, char* channel, char* subs, char* views) //social icon if(strcmp(type, "t") == 0) { - panel->drawBitmap(18, 2, icon_twitch, 28, 30, color_twitch); + panel->drawBitmap(18, 2, icon_twitch, 28, 30, color_twitch); } else if (strcmp(type, "y") == 0) { - panel->drawBitmap(16, 5, icon_youtube_base, 32, 24, color_youtube); - panel->drawBitmap(28, 11, icon_youtube_play, 9, 12, 0xFFFF); + panel->drawBitmap(16, 5, icon_youtube_base, 32, 24, color_youtube); + panel->drawBitmap(28, 11, icon_youtube_play, 9, 12, 0xFFFF); + } else if(strcmp(type, "i") == 0) { + uint16_t position = 0; + for(uint8_t y = 2; y < 32; y++) { + for(uint8_t x = 17; x < 47; x++) { + panel->drawPixel(x, y, icon_instagram[position]); + position++; + } + } } //text props @@ -525,9 +1164,11 @@ void display_social_channel(char* type, char* channel, char* subs, char* views) panel->setTextColor(0xFFFF); panel->setTextSize(1); - panel->getTextBounds(channel, 0, 0, &x1, &y1, &width, &height); - panel->setCursor(64 > width ? .5 * (64 - width) : 0, 42); - panel->write(channel); + if(channel && strlen(channel) > 0) { + panel->getTextBounds(channel, 0, 0, &x1, &y1, &width, &height); + panel->setCursor(64 > width ? .5 * (64 - width) : 0, 42); + panel->write(channel); + } int textbox_start_position = 0; int textbox_offset = 0; @@ -536,19 +1177,21 @@ void display_social_channel(char* type, char* channel, char* subs, char* views) if(strcmp(subs, "") != 0 && strcmp(subs, "0") != 0) { if(strcmp(type, "t") == 0) textbox_offset = 10; - else if (strcmp(type, "y") == 0) + else if (strcmp(type, "y") == 0 || strcmp(type, "i") == 0) textbox_offset = 8; panel->getTextBounds(subs, 0, 0, &x1, &y1, &width, &height); textbox_start_position = 64 - textbox_offset > width ? .5 * (64 - textbox_offset - width) : 0; panel->setCursor(textbox_start_position + textbox_offset, 52); panel->write(subs); - } - //subs icon - if(strcmp(type, "t") == 0) - panel->drawBitmap(textbox_start_position, 45, icon_heart, 8, 7, color_twitch); - else if (strcmp(type, "y") == 0) - panel->drawBitmap(textbox_start_position, 45, icon_bell, 6, 7, 0xFFFF); + //subs icon + if(strcmp(type, "t") == 0) + panel->drawBitmap(textbox_start_position, 45, icon_heart, 8, 7, color_twitch); + else if (strcmp(type, "y") == 0) + panel->drawBitmap(textbox_start_position, 45, icon_bell, 6, 7, 0xFFFF); + else if(strcmp(type, "i") == 0) + panel->drawBitmap(textbox_start_position, 45, icon_person, 6, 7, 0xFFFF); + } //views if(strcmp(views, "") != 0 && strcmp(views, "0") != 0) { @@ -557,6 +1200,8 @@ void display_social_channel(char* type, char* channel, char* subs, char* views) textbox_offset = 8; else if (strcmp(type, "y") == 0) textbox_offset = 11; + else if(strcmp(type, "i") == 0) + textbox_offset = 10; panel->getTextBounds(views, 0, 0, &x1, &y1, &width, &height); textbox_start_position = 64 - textbox_offset > width ? .5 * (64 - textbox_offset - width) : 0; panel->setCursor(textbox_start_position + textbox_offset, 62); @@ -565,8 +1210,10 @@ void display_social_channel(char* type, char* channel, char* subs, char* views) //views icon if(strcmp(type, "t") == 0) panel->drawBitmap(textbox_start_position, 55, icon_person, 6, 7, color_coral); - if(strcmp(type, "y") == 0) + else if(strcmp(type, "y") == 0) panel->drawBitmap(textbox_start_position, 55, icon_eye, 9, 7, 0xFFFF); + else if(strcmp(type, "i") == 0) + panel->drawBitmap(textbox_start_position, 55, icon_heart, 8, 7, 0xF988); } display_overlay(); @@ -586,23 +1233,23 @@ void display_clock_analog() { //hour hand int hourX = (hour >= 6 ? 31 : 32); - int hourY = (hour >= 9 || hour <= 3 ? 31 : 32); + int hourY = (hour >= 9 || hour < 3 ? 31 : 32); float radiant_hour = radians(180 - (30 * hour) - (minute / 2)); panel->drawLine(hourX + 15 * sin(radiant_hour), hourY + 15 * cos(radiant_hour), hourX, hourY, 0xFFFF); //minute hand - int minuteX = (minute >= 30 ? 31 : 32); - int minuteY = (minute >= 45 || minute <= 15 ? 31 : 32); + int minuteX = (minute > 30 || minute == 0 ? 31 : 32); + int minuteY = (minute > 45 || minute <= 15 ? 31 : 32); float radiant_minute = radians(180 - (6 * minute)); - panel->drawLine(minuteX + 23 * sin(radiant_minute), minuteY + 23 * cos(radiant_minute), minuteX, minuteY, 0xFFFF); + panel->drawLine(minute == 30 || minute == 0 ? minuteX : minuteX + 23 * sin(radiant_minute), minute == 15 || minute == 45 ? minuteY : minuteY + 23 * cos(radiant_minute), minuteX, minuteY, 0xFFFF); //second hand if(clock_seconds) { int second = rtc_int.getSecond(); - int secondX = (second >= 30 ? 31 : 32); - int secondY = (second >= 45 || second <= 15 ? 31 : 32); + int secondX = (second > 30 || second == 0 ? 31 : 32); + int secondY = (second > 45 || second <= 15 ? 31 : 32); float radiant_second = radians(180 - (6 * second)); - panel->drawLine(secondX + 28 * sin(radiant_second), secondY + 28 * cos(radiant_second), secondX, secondY, 0xF800); + panel->drawLine(second == 30 || second == 0 ? secondX : secondX + 28 * sin(radiant_second), second == 15 || second == 45 ? secondY : secondY + 28 * cos(radiant_second), secondX, secondY, 0xF800); } //marks @@ -665,6 +1312,7 @@ void display_clock_digital(bool withDate) { panel->setCursor( x_start, y_start); panel->write(clockText); + panel->setTextSize(1); display_overlay(); @@ -695,6 +1343,7 @@ void display_clock_big() { panel->setCursor(14, 54); panel->write(clockText); + panel->setTextSize(1); panel->setFont(&Font4x7Fixed); display_overlay(); @@ -703,6 +1352,15 @@ void display_clock_big() { } void display_current() { + if(menu != MENU_NONE) { + display_menu(); + return; + } + + if(current_mode != MODE_SOCIALS) { + reset_loading(); + } + switch(current_mode) { case MODE_SOCIALS: //go to next mode if wifi wont be connected @@ -713,6 +1371,7 @@ void display_current() { } if(socials_channels.size() > 0) { + reset_loading(); if(socials_channels.size() < socials_channel_current) socials_channel_current = 0; @@ -723,17 +1382,8 @@ void display_current() { socials_channels[socials_channel_current].views ); } else { - char * channel_type = strdup(""); - char * channel_name = strdup("Loading"); - char * channel_follower = strdup(""); - char * channel_views = strdup(""); - - display_social_channel(channel_type, channel_name, channel_follower, channel_views); - - free(channel_type); - free(channel_name); - free(channel_follower); - free(channel_views); + display_loading(); + display_overlay(); } break; @@ -868,11 +1518,57 @@ void IRAM_ATTR trigger_rot3_btn() { ** Setup ** *************/ -//Initialize GPIO Pins -void gpio_setup() { +//Load config data from internal filesystem +void spiffs_setup() { + if(SPIFFS.begin()) { + if(SPIFFS.exists("/wifi.conf")) { + File file = SPIFFS.open("/wifi.conf"); + if(!file.isDirectory()) { + size_t filesize = file.size(); + char buffer[filesize]; + file.readBytes(buffer, filesize); + buffer[filesize] = '\0'; + free(wifi_ap_password); + wifi_ap_password = strdup(buffer); + } + } + if(SPIFFS.exists("/socials.conf")) { + File file = SPIFFS.open("/socials.conf"); + if(!file.isDirectory()) { + size_t filesize = file.size(); + char buffer[filesize]; + file.readBytes(buffer, filesize); + buffer[filesize] = '\0'; + free(socials_api_key); + socials_api_key = strdup(buffer); + } + } + } + SPIFFS.end(); +} + +// Show boot sequence +void setup_boot_sequence() { + for(int row = 0; row < boot_sequence.size(); row++) { - //Turning off onboard led - pinMode(48, PULLDOWN); + for(int pixel = 0; pixel < boot_sequence[row].pixels.size(); pixel++) { + panel->drawPixel(boot_sequence[row].pixels[pixel], boot_sequence[row].row, 0xFFFF); + } + + if(PANEL_DOUBLE_BUFFER) { + panel->flipDMABuffer(); + + for(int pixel = 0; pixel < boot_sequence[row].pixels.size(); pixel++) { + panel->drawPixel(boot_sequence[row].pixels[pixel], boot_sequence[row].row, 0xFFFF); + } + } + + delay(7); + } +} + +// Initialize GPIO Pins +void gpio_setup() { //Setting PinModes pinMode(GPIO_BTN1, PULLUP); @@ -907,7 +1603,9 @@ void gpio_setup() { Wire.begin(); //SPI - SPI.begin(GPIO_SD_SCLK, GPIO_SD_MISO, GPIO_SD_MOSI, GPIO_SD_CS); + spi = new SPIClass(HSPI); + spi->begin(GPIO_SD_SCLK, GPIO_SD_MISO, GPIO_SD_MOSI, GPIO_SD_CS); + } // Initialize LED Matrix @@ -946,29 +1644,42 @@ void panel_setup() { panel->clearScreen(); panel->setPanelBrightness(brightness); panel->setFont(&Font4x7Fixed); + panel->setTextSize(1); panel->setTextWrap(false); - if(PANEL_DOUBLE_BUFFER) + if(PANEL_DOUBLE_BUFFER) { panel->flipDMABuffer(); + panel->clearScreen(); + } } -//Server setup +// Server setup void server_setup() { if(WiFi.getMode() != WIFI_MODE_NULL) { DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "*"); + server.begin(); + server.onNotFound([](AsyncWebServerRequest *request) { if(request->method() == HTTP_OPTIONS) request->send(200); - else - request->send(404, "application/json", String()); + //Captive portal but no on connected ap only on softAP + else if(wifi_host && strcmp(request->host().c_str(), WiFi.localIP().toString().c_str()) != 0) { + if(sd_connected() && SD.exists(server_home_file)) { + request->send(SD, server_home_file, String()); + } else { + request->send(500, "text/html", missing_sd); + } + } else { + request->send(404, "text/html", not_found); + } }); - //API trigger + //API GET server.on("/api/display", HTTP_GET, [](AsyncWebServerRequest * request) { if(verify_api_key(request)) { AsyncJsonResponse *response = new AsyncJsonResponse(); @@ -979,13 +1690,49 @@ void server_setup() { root["displayMode"] = current_mode; root["brightness"] = brightness; root["version"] = VERSION; - root["freeRam"] = ESP.getFreeHeap(); + root["freeMemory"] = ESP.getFreeHeap(); + root["freeSPIMemory"] = ESP.getFreePsram(); root["refreshRate"] = panel->calculated_refresh_rate; + root["animation"] = animation_enabled; + root["animationTime"] = animation_time; + root["diashow"] = diashow_enabled; + root["diashowTime"] = diashow_time; + root["diashowModes"] = diashow_modes; + + response->setLength(); + request->send(response); + } else { + request->send(403); + } + }); + + server.on("/api/images", HTTP_GET, [](AsyncWebServerRequest * request) { + if(verify_api_key(request)) { + AsyncJsonResponse *response = new AsyncJsonResponse(); + response->setContentType("application/json"); + response->setCode(200); + JsonVariant& root = response->getRoot(); + + root["displayImage"] = selected_image; + root["imageNumber"] = image_index.size(); + root["imagePrefixMax"] = image_prefix_max; + root["imageLoaded"] = image_loaded; + JsonArray images = root.createNestedArray("images"); + + char nameBuffer[255]; + for(int i = 0; i < image_index.size(); i++) { + memcpy(nameBuffer, image_index[i].folder + 8, strlen(image_index[i].folder) - 7); + + JsonObject image = images.createNestedObject(); + image["prefix"] = image_index[i].prefix; + image["folder"] = nameBuffer; + image["animated"] = image_index[i].animated; + } response->setLength(); request->send(response); } else { - request->send(403, "application/json", "{}"); + request->send(403); } }); @@ -998,12 +1745,14 @@ void server_setup() { root["request"] = socials_request; root["server"] = socials_request_server; + root["apiKey"] = socials_api_key; root["displayChannel"] = socials_channel_current; + root["channelNumber"] = socials_channels.size(); response->setLength(); request->send(response); } else { - request->send(403, "application/json", "{}"); + request->send(403); } }); @@ -1029,53 +1778,58 @@ void server_setup() { response->setLength(); request->send(response); } else { - request->send(403, "application/json", "{}"); + request->send(403); } }); - server.on("/api/wifi", HTTP_GET, [](AsyncWebServerRequest * request) { + server.on("/api/wifi/available", HTTP_GET, [](AsyncWebServerRequest * request) { if(verify_api_key(request)) { AsyncJsonResponse *response = new AsyncJsonResponse(); response->setContentType("application/json"); response->setCode(200); JsonVariant& root = response->getRoot(); - root["wifiAP"] = wifi_host; - root["wifiAPSSID"] = wifi_ap_ssid; - root["wifiAPPassword"] = wifi_ap_password; - root["wifiAPIP"] = WiFi.softAPIP(); - - root["wifiConnect"] = wifi_connect; - root["wifiSetupComplete"] = wifi_setup_complete; - root["wifiSSID"] = wifi_ssid; - root["wifiIP"] = WiFi.localIP(); - root["wifiHostname"] = WiFi.getHostname(); + JsonArray networks = root.createNestedArray("networks"); + for(int i = 0; i < available_networks.size(); i++) { + JsonObject network = networks.createNestedObject(); + network["ssid"] = available_networks[i].ssid; + network["rssi"] = available_networks[i].rssi; + network["encryption"] = available_networks[i].encryption; + } response->setLength(); request->send(response); - } else { - request->send(403, "application/json", "{}"); - } - }); - server.on("/api/refresh", HTTP_POST, [](AsyncWebServerRequest * request) { - if(verify_api_key(request)) { - display_change = true; - request->send(200, "application/json"); + ms_wifi_scan_requested = millis() + 500; } else { - request->send(403, "application/json"); + request->send(403); } }); - server.on("/api/apiKey", HTTP_DELETE, [](AsyncWebServerRequest * request) { + server.on("/api/wifi", HTTP_GET, [](AsyncWebServerRequest * request) { if(verify_api_key(request)) { - preferences.begin(PREFERENCES_NAMESPACE, false); - api_key = generate_uid(); - preferences.putString("api_key", api_key); - preferences.end(); - request->send(200); + AsyncJsonResponse *response = new AsyncJsonResponse(); + response->setContentType("application/json"); + response->setCode(200); + JsonVariant& root = response->getRoot(); + + root["ap"] = wifi_host; + root["apSSID"] = wifi_ap_ssid; + root["apPassword"] = wifi_ap_password; + root["apIP"] = WiFi.softAPIP(); + + root["connect"] = wifi_connect; + root["setupComplete"] = wifi_setup_complete; + root["ssid"] = wifi_ssid; + root["ip"] = WiFi.localIP(); + root["hostname"] = WiFi.getHostname(); + root["rssi"] = WiFi.RSSI(); + root["mac"] = WiFi.macAddress(); + + response->setLength(); + request->send(response); } else { - request->send(403, "application/json"); + request->send(403); } }); @@ -1098,22 +1852,179 @@ void server_setup() { request->send(response); }); - server.on("/api/reset", HTTP_POST, [](AsyncWebServerRequest * request) { + server.on("/api/apiKey", HTTP_DELETE, [](AsyncWebServerRequest * request) { if(verify_api_key(request)) { preferences.begin(PREFERENCES_NAMESPACE, false); - preferences.clear(); + free(api_key); + api_key = generate_uid(); + preferences.putString("api_key", api_key); preferences.end(); request->send(200); - requested_restart = true; } else { - request->send(403, "application/json"); + request->send(403); } }); - //API Data processors + //API JSON POST + server.addHandler(new AsyncCallbackJsonWebHandler("/api/display", [](AsyncWebServerRequest * request, JsonVariant &json) { + if(verify_api_key(request)) { + JsonObject body = json.as(); + preferences.begin(PREFERENCES_NAMESPACE); + + if(body.containsKey("displayMode") && body["displayMode"].is() && body["displayMode"] >= 0) { + current_mode = body["displayMode"].as() > DISPLAY_MODE_NUMBER ? MODE_IMAGES : static_cast(body["displayMode"].as()); + preferences.putUInt("current_mode", current_mode); + } + if(body.containsKey("brightness") && body["brightness"].is()) { + brightness = body["brightness"].as() > 248 ? 248 : body["brightness"].as() < 16 ? 16 : body["brightness"].as(); + preferences.putUInt("brightness", brightness); + panel->setBrightness(brightness); + } + if(body.containsKey("animation") && body["animation"].is()) { + animation_enabled = body["animation"].as(); + preferences.putBool("animation", animation_enabled); + } + if(body.containsKey("diashow") && body["diashow"].is()) { + diashow_enabled = body["diashow"].as(); + preferences.putBool("diashow", diashow_enabled); + } + if(body.containsKey("diashowModes") && body["diashowModes"].is()) { + diashow_modes = body["diashowModes"].as(); + preferences.putBool("diashow_modes", diashow_modes); + } + if(body.containsKey("animationTime") && body["animationTime"].is() && body["animationTime"] > 0) { + animation_time = body["animationTime"].as(); + preferences.putUInt("animation_time", animation_time); + } + if(body.containsKey("diashowTime") && body["diashowTime"].is() && body["diashowTime"] > 0) { + diashow_time = body["diashowTime"].as(); + preferences.putUInt("diashow_time", diashow_time); + } + + preferences.end(); + request->send(200); + display_change = true; + } else { + request->send(403); + } + })); + + server.addHandler(new AsyncCallbackJsonWebHandler("/api/images", [](AsyncWebServerRequest * request, JsonVariant &json) { + if(verify_api_key(request)) { + JsonObject body = json.as(); + preferences.begin(PREFERENCES_NAMESPACE); + + if(body.containsKey("displayImage") && body["displayImage"].is() && body["displayImage"].as() >= 0) { + current_mode = MODE_IMAGES; + selected_image = body["displayImage"].as(); + if(sd_connected() && image_index.size() > 0) { + if(selected_image >= image_index.size()) + selected_image = 0; + image_loaded = sd_load_image(image_index[selected_image]); + } else { + image_loaded = false; + } + preferences.putUInt("selected_image", selected_image); + } + + if(body.containsKey("imageOperations") && body["imageOperations"].is()) { + JsonArray imageOperations = body["imageOperations"].as(); + char buffer[255]; + for(int i = 0; i < imageOperations.size(); i++) { + + const char* src = imageOperations[i]["src"].as(); + const char* dst = imageOperations[i]["dst"].as(); + + if(src) { + strcpy(buffer, images_folder); + strcat(buffer, "/"); + strcat(buffer, src); + + size_t srcLen = strlen(buffer) + 1; + char src[srcLen]; + strncpy(src, buffer, srcLen); + + if(dst) { + strcpy(buffer, images_folder); + strcat(buffer, "/"); + strcat(buffer, dst); + + file_operation operation = { + strdup(src), + strdup(buffer) + }; + file_operations.emplace_back(operation); + } else { + file_operation operation = { + strdup(src) + }; + file_operations.emplace_back(operation); + } + } + } + } + + display_change = true; + + preferences.end(); + request->send(202); + } else { + request->send(403); + } + })); + + server.addHandler(new AsyncCallbackJsonWebHandler("/api/socials", [](AsyncWebServerRequest * request, JsonVariant &json) { + if(verify_api_key(request)) { + JsonObject body = json.as(); + + const char* json_request = body["request"].as(); + const char* json_server = body["server"].as(); + const char* json_apiKey = body["apiKey"].as(); + + preferences.begin(PREFERENCES_NAMESPACE); + + if(body.containsKey("displayChannel") && body["displayChannel"].is() && body["displayChannel"] >= 0) { + current_mode = MODE_SOCIALS; + preferences.putUInt("current_mode", current_mode); + socials_channel_current = body["displayChannel"].as(); + if(socials_channel_current >= socials_channels.size()) + socials_channel_current = 0; + preferences.putUInt("current_social", socials_channel_current); + } + if(json_request) { + free(socials_request); + socials_request = strdup(json_request); + preferences.putString("socials_request", socials_request); + ms_socials_request = millis() + 500; + } + if(json_server) { + free(socials_request_server); + socials_request_server = strdup(json_server); + preferences.putString("socials_server", socials_request_server); + ms_socials_request = millis() + 500; + } + if(json_apiKey) { + free(socials_api_key); + socials_api_key = strdup(json_apiKey); + preferences.putString("socials_api_key", socials_api_key); + ms_socials_request = millis() + 500; + } + + preferences.end(); + request->send(200); + display_change = true; + } else { + request->send(403); + } + })); + server.addHandler(new AsyncCallbackJsonWebHandler("/api/time", [](AsyncWebServerRequest * request, JsonVariant &json) { if(verify_api_key(request)) { JsonObject body = json.as(); + + const char* json_timezone = body["timezone"].as(); + const char* json_ntpServer = body["ntpServer"].as(); + preferences.begin(PREFERENCES_NAMESPACE); if(body.containsKey("seconds") && body["seconds"].is()) { @@ -1136,26 +2047,26 @@ void server_setup() { update_time = body["updateTime"].as(); preferences.putBool("update_time", update_time); } - if(body.containsKey("timezone") && body["timezone"].is()) { + if(json_timezone) { free(timezone); - timezone = strdup(body["timezone"]); + timezone = strdup(json_timezone); preferences.putString("timezone", timezone); setenv("TZ", timezone, 1); tzset(); } - if(body.containsKey("ntpServer") && body["ntpServer"].is()) { + if(json_ntpServer) { free(ntp_server); - ntp_server = strdup(body["ntpServer"]); + ntp_server = strdup(json_ntpServer); preferences.putString("ntpServer", ntp_server); } if(body.containsKey("displayMode") && body["displayMode"].is()) { current_mode = MODE_CLOCK; - preferences.putInt("current_mode", current_mode); - current_clock_mode = body["displayMode"] > CLOCK_TYPE_NUMBER ? CLOCK_ANALOG : static_cast(body["displayMode"]); - preferences.putInt("clock_mode", current_clock_mode); + preferences.putUInt("current_mode", current_mode); + current_clock_mode = body["displayMode"].as() > CLOCK_TYPE_NUMBER ? CLOCK_ANALOG : static_cast(body["displayMode"].as()); + preferences.putUInt("clock_mode", current_clock_mode); } if(body.containsKey("time") && body["time"].is() && body["time"] > 0) { - rtc_int.setTime(body["time"]); + rtc_int.setTime(body["time"].as()); update_time = false; preferences.putBool("update_time", false); if(rtc_ext_enabled) { @@ -1163,118 +2074,301 @@ void server_setup() { rtc_ext_adjust = true; } } + + preferences.end(); + request->send(200); //get current time from ntp server if wanted with new settings - if(update_time && WiFi.waitForConnectResult(50) == WL_CONNECTED) { - configTzTime(timezone, ntp_server); - ms_rtc_ext_adjust = millis() + 10000; - rtc_ext_adjust = true; + if(update_time) { + wifi_setup_complete = false; } - - preferences.end(); display_change = true; - request->send(200, "application/json"); } else { - request->send(403, "application/json"); + request->send(403); } })); - server.addHandler(new AsyncCallbackJsonWebHandler("/api/display", [](AsyncWebServerRequest * request, JsonVariant &json) { + + server.addHandler(new AsyncCallbackJsonWebHandler("/api/wifi", [](AsyncWebServerRequest * request, JsonVariant &json) { if(verify_api_key(request)) { JsonObject body = json.as(); + + const char* json_ssid = body["ssid"].as(); + const char* json_apSSID = body["apSSID"].as(); + const char* json_password = body["password"].as(); + const char* json_apPassword = body["apPassword"].as(); + preferences.begin(PREFERENCES_NAMESPACE); - if(body.containsKey("displayMode") && body["displayMode"].is()) { - current_mode = body["displayMode"] > DISPLAY_MODE_NUMBER ? MODE_IMAGES : static_cast(body["displayMode"]); - preferences.putInt("current_mode", current_mode); + if(body.containsKey("connect") && body["connect"].is()) { + wifi_connect = body["connect"].as(); + preferences.putBool("wifi_connect", wifi_connect); } - if(body.containsKey("brightness") && body["brightness"].is()) { - brightness = body["brightness"] > 248 ? 248 : body["brightness"] < 16 ? 16 : body["brightness"]; - preferences.putShort("brightness", brightness); - panel->setBrightness(brightness); + if(body.containsKey("ap") && body["ap"].is()) { + wifi_host = body["ap"].as(); + preferences.putBool("wifi_host", wifi_host); } - + if(json_ssid) { + free(wifi_ssid); + wifi_ssid = strdup(json_ssid); + preferences.putString("wifi_ssid", wifi_ssid); + } + if(json_apSSID) { + free(wifi_ap_ssid); + wifi_ap_ssid = strdup(json_apSSID); + preferences.putString("wifi_ap_ssid", wifi_ap_ssid); + } + if(json_password) { + free(wifi_password); + wifi_password = strdup(json_password); + preferences.putString("wifi_password", wifi_password); + } + if(json_apPassword) { + free(wifi_ap_password); + wifi_ap_password = strdup(json_apPassword); + preferences.putString("wifi_ap_pass", wifi_ap_password); + } + preferences.end(); - display_change = true; - request->send(200, "application/json"); + request->send(200); + + ms_wifi_restart = millis() + 500; } else { - request->send(403, "application/json"); + request->send(403); } })); - //webserver + //API POST + server.on("/api/refresh", HTTP_POST, [](AsyncWebServerRequest * request) { + if(verify_api_key(request)) { + request->send(200); + display_change = true; + } else { + request->send(403); + } + }); + + server.on("/api/reset", HTTP_POST, [](AsyncWebServerRequest * request) { + if(verify_api_key(request)) { + preferences.begin(PREFERENCES_NAMESPACE, false); + preferences.clear(); + preferences.end(); + request->send(200); + ms_requested_restart = millis() + 500; + } else { + request->send(403); + } + }); + + server.on("/api/restart", HTTP_POST, [](AsyncWebServerRequest * request) { + if(verify_api_key(request)) { + request->send(200); + ms_requested_restart = millis() + 500; + } else { + request->send(403); + } + }); + + //File uploads + server.on("/api/file", HTTP_POST, [](AsyncWebServerRequest * request) { + + if(uploading_file) { + uploading_file.close(); + } + + if(!verify_api_key(request)) { + request->send(403); + return; + } + + if(!upload_success) { + request->send(500); + upload_success = true; + return; + } + + //add file operations for moving from temp upload path + if(!request->hasParam("type", true)) { + request->send(400, "application/json", "{\"message\":\"parameter type is mandatory\"}"); + return; + } + + AsyncWebParameter* type_param = request->getParam("type", true); + const char * type = type_param->value().c_str(); + + int params = request->params(); + for(int i=0;igetParam(i); + if(p->isFile() && strlen(p->name().c_str()) > 3){ + char src_path[255]; + strcpy(src_path, upload_directory); + strcat(src_path, "/"); + strcat(src_path, p->value().c_str()); + + char dst_path[255]; + + if(strcmp(type, "images") == 0) { + strcpy(dst_path, images_folder); + } else if(strcmp(type, "webinterface") == 0) { + strcpy(dst_path, server_home_dir); + } else if(strcmp(type, "firmware") == 0) { + strcpy(dst_path, update_file); + } else { + request->send(400, "application/json", "{\"message\":\"unexpected value for parameter type - expected 'images' | 'webinterface' | 'firmware'\"}"); + return; + } + + if(strcmp(type, "firmware") != 0) { + const char * dst_value = p->name().c_str(); + if(dst_value[0] != '/') { + strcat(dst_path, "/"); + } + strcat(dst_path, dst_value); + } + + file_operation operation = { + strdup(src_path), + strdup(dst_path) + }; + + file_operations.emplace_back(operation); + } + } + + request->send(202); + + }, [](AsyncWebServerRequest * request, String filename, size_t index, uint8_t *data, size_t length, bool final) { + if(upload_success && verify_api_key(request) && ((index == 0 && !uploading_file) || (index != 0 && uploading_file)) && sd_connected()) { + char tmp_path[255]; + strcpy(tmp_path, upload_directory); + strcat(tmp_path, "/"); + strcat(tmp_path, filename.c_str()); + + //remove old file before writing and setting current upload as blocking + if(index == 0) { + remove_recursive(SD, tmp_path); + if(ensure_folder(SD, upload_directory)) { + uploading_file = SD.open(tmp_path, "w", true); + } else { + upload_success = false; + } + } + + //check if still writing to right file + if(strcmp(tmp_path, uploading_file.path()) == 0) { + bool upload_success = uploading_file.write(data, length); + } + + //resetting current blocking upload + if(final && upload_success) { + uploading_file.close(); + } + } else { + upload_success = false; + } + }); + + //React webserver server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - if(sd_connected && SD.exists("/webserver/index.html")) { - request->send(SD, "/webserver/index.html", String()); + if(sd_connected() && SD.exists(server_home_file)) { + request->send(SD, server_home_file, String()); } else { - request->send(200, "text/plain", "SD Card or Files missing"); + request->send(500, "text/html", missing_sd); } }); - server.serveStatic("/", SD, "/webserver").setDefaultFile("index.html").setCacheControl("max-age=600"); + server.on(server_version_file, HTTP_GET, [](AsyncWebServerRequest *request) { + if(sd_connected() && SD.exists(server_version_file)) { + request->send(SD, server_version_file, "application/json"); + } else { + request->send(500, "text/html", missing_sd); + } + }); + + server.serveStatic("/", SD, server_home_dir).setCacheControl("max-age=600"); + + server_setup_complete = true; } } // Initialize Wifi if enabled -void wifi_setup() { +void wifi_setup(bool ignoreAP = false) { + WiFi.persistent(false); WiFi.setHostname(WIFI_HOSTNAME); WiFi.setAutoReconnect(true); - ms_wifi_reconnect = millis() + 60000; + WiFi.disconnect(true, true); + + if(!ignoreAP) + WiFi.softAPdisconnect(true); + + ms_wifi_reconnect = millis() + 30000; wifi_setup_complete = true; + + dns_server.stop(); + dns_server.setErrorReplyCode(DNSReplyCode::NoError); + + delay(200); //wait to properly disconnect if(wifi_connect) { WiFi.begin(wifi_ssid, wifi_password); - WiFi.waitForConnectResult(250); + WiFi.waitForConnectResult(200); wifi_setup_complete = false; } - if(wifi_host) { + if(wifi_host && !ignoreAP) { WiFi.softAP(wifi_ap_ssid, wifi_ap_password); - //TODO DNS not yet working - dnsServer.setTTL(300); - dnsServer.setErrorReplyCode(AsyncDNSReplyCode::ServerFailure); - dnsServer.start(53, "*", WiFi.softAPIP()); + dns_server.start(53, "*", WiFi.softAPIP()); + } + + if(!server_setup_complete) { + delay(200); //wait to properly start + server_setup(); } } -//Load preferences from flash +// Load preferences from flash void preferences_load() { preferences.begin(PREFERENCES_NAMESPACE, false); //settings if(preferences.isKey("brightness")) - brightness = preferences.getShort("brightness", brightness); + brightness = preferences.getUInt("brightness", brightness); if(preferences.isKey("current_mode")) - current_mode = static_cast(preferences.getInt("current_mode", current_mode)); + current_mode = static_cast(preferences.getUInt("current_mode", current_mode)); if(preferences.isKey("selected_image")) - selected_image = preferences.getInt("selected_image", selected_image); + selected_image = preferences.getUInt("selected_image", selected_image); + if(preferences.isKey("animation")) + animation_enabled = preferences.getBool("animation", animation_enabled); + if(preferences.isKey("diashow")) + diashow_enabled = preferences.getBool("diashow", diashow_enabled); + if(preferences.isKey("animation_time")) + animation_time = preferences.getUInt("animation_time", animation_time); + if(preferences.isKey("diashow_time")) + diashow_time = preferences.getUInt("diashow_time", diashow_time); + if(preferences.isKey("diashow_modes")) + diashow_modes = preferences.getBool("diashow_modes", diashow_modes); //wifi if(preferences.isKey("wifi_connect")) wifi_connect = preferences.getBool("wifi_connect", WIFI_CONNECT_DEFAULT); - if(preferences.isKey("wifi_host")) wifi_host = preferences.getBool("wifi_host", WIFI_HOST_DEFAULT); - if(preferences.isKey("wifi_ssid")) { free(wifi_ssid); wifi_ssid = strdup(preferences.getString("wifi_ssid", WIFI_SSID_DEFAULT).c_str()); } - if(preferences.isKey("wifi_ap_ssid")) { free(wifi_ap_ssid); wifi_ap_ssid = strdup(preferences.getString("wifi_ap_ssid", WIFI_AP_SSID_DEFAULT).c_str()); - } - + } if(preferences.isKey("wifi_password")) { free(wifi_password); wifi_password = strdup(preferences.getString("wifi_password", WIFI_PASSWORD_DEFAULT).c_str()); - } - - if(preferences.isKey("wifi_ap_password")) { + } + if(preferences.isKey("wifi_ap_pass")) { free(wifi_ap_password); - wifi_ap_password = strdup(preferences.getString("wifi_ap_password", WIFI_AP_PASSWORD_DEFAULT).c_str()); + wifi_ap_password = strdup(preferences.getString("wifi_ap_pass", WIFI_AP_PASSWORD_DEFAULT).c_str()); } //ntp @@ -1282,15 +2376,12 @@ void preferences_load() { free(ntp_server); ntp_server = strdup(preferences.getString("ntpServer", ntp_server).c_str()); } - if(preferences.isKey("timezone")) { free(timezone); timezone = strdup(preferences.getString("timezone", timezone).c_str()); } - if(preferences.isKey("update_time")) update_time = preferences.getBool("update_time", update_time); - if(preferences.isKey("time_format24")) time_format24 = preferences.getBool("time_format24", time_format24); @@ -1309,30 +2400,24 @@ void preferences_load() { free(socials_request); socials_request = strdup(preferences.getString("socials_request", socials_request).c_str()); } - if(preferences.isKey("socials_api_key")) { free(socials_api_key); socials_api_key = strdup(preferences.getString("socials_api_key", socials_api_key).c_str()); } - if(preferences.isKey("socials_server")) { free(socials_request_server); socials_request_server = strdup(preferences.getString("socials_server", socials_request_server).c_str()); } - if(preferences.isKey("current_social")) - socials_channel_current = preferences.getInt("current_social", socials_channel_current); + socials_channel_current = preferences.getUInt("current_social", socials_channel_current); //clock if(preferences.isKey("clock_mode")) - current_clock_mode = static_cast(preferences.getInt("clock_mode", current_clock_mode)); - + current_clock_mode = static_cast(preferences.getUInt("clock_mode", current_clock_mode)); if(preferences.isKey("clock_seconds")) clock_seconds = preferences.getBool("clock_seconds", clock_seconds); - if(preferences.isKey("clock_blink")) clock_blink = preferences.getBool("clock_blink", clock_blink); - if(preferences.isKey("clock_year")) clock_year = preferences.getBool("clock_year", clock_year); @@ -1340,7 +2425,7 @@ void preferences_load() { preferences.end(); } -//Init rtc +// Init rtc void time_setup() { setenv("TZ", timezone, 1); @@ -1356,6 +2441,7 @@ void time_setup() { } } +// Reset input values before going into loop void booted_setup() { btn1_pressed = false; btn2_pressed = false; @@ -1367,7 +2453,7 @@ void booted_setup() { rot1_clicks = 0; rot2_clicks = 0; rot3_clicks = 0; - requested_restart = false; + ms_requested_restart = 0; if(sd_connected() && image_index.size() > 0) { if(selected_image >= image_index.size()) @@ -1375,21 +2461,24 @@ void booted_setup() { image_loaded = sd_load_image(image_index[selected_image]); } - booted = true; - display_current(); + ms_current = millis(); + ms_animation = ms_current + animation_time; + ms_diashow = ms_current + diashow_time; + + display_overlay(OVERLAY_TEXT, current_mode == MODE_IMAGES ? "Pictures" : current_mode == MODE_CLOCK ? "Clock" : "Socials"); } void setup() { - Serial.begin(9600); //TODO remove after testing - + spiffs_setup(); //call before preferences_load to not overwrite user defined preferences preferences_load(); + panel_setup(); //depends on preferences + setup_boot_sequence(); //depends on panel gpio_setup(); - time_setup(); - wifi_setup(); - server_setup(); - panel_setup(); + time_setup(); //depends on gpio and preferences + wifi_setup(); //depends on preferences (and gpio and wifi for server) + firmware_update(); //depends on gpio and panel - booted_setup(); //TODO move after boot sequence if implemented + booted_setup(); //finish setup and transfer to loop } @@ -1398,50 +2487,200 @@ void setup() { /*********** ** Loop ** ************/ - void loop() { ms_current = millis(); - //display changes - if(booted) { - - //diashow speed rot - if(rot1_clicks != 0) { + //diashow and animation routine + if(menu == MENU_NONE) { + if(current_mode == MODE_IMAGES) { + if(diashow_enabled && ms_diashow < ms_current) { + if(sd_connected() && image_index.size() > 0) { + if(++selected_image >= image_index.size()) { + selected_image = 0; + if(diashow_modes && socials_channels.size() > 0) { + current_mode = MODE_SOCIALS; + socials_channel_current = 0; + } + } + image_loaded = sd_load_image(image_index[selected_image]); + } else { + image_loaded = false; + } + ms_diashow = ms_current + diashow_time; + display_change = true; + } - rot1_clicks = 0; + if(animation_enabled && image_loaded && ms_animation < ms_current) { + display_next_frame(); + ms_animation = ms_current + animation_time; + display_change = true; + } + } else if(current_mode == MODE_SOCIALS && diashow_enabled && ms_diashow < ms_current) { + if(socials_channels.size() > 0 && ++socials_channel_current >= socials_channels.size()) { + socials_channel_current = 0; + if(diashow_modes && image_index.size() > 0) { + current_mode = MODE_IMAGES; + selected_image = 0; + image_loaded = sd_load_image(image_index[selected_image]); + } + } + ms_diashow = ms_current + diashow_time; + display_change = true; } + } + + //brightness rot + if(rot1_clicks != 0) { + if(menu != MENU_DATETIME) { + int16_t new_brightness = brightness + rot1_clicks * 8; + brightness = new_brightness > 248 ? 248 : new_brightness < 16 ? 16 : new_brightness; + display_overlay(OVERLAY_BRIGHTNESS); - //animation speed rot - if(rot2_clicks != 0) { - //TODO + preferences.begin(PREFERENCES_NAMESPACE); + preferences.putUInt("brightness", brightness); + preferences.end(); + } else { + if(!menu_time_changed) + menu_copy_time(); - rot2_clicks = 0; + int8_t new_time; + + switch(menu_selection) { + case 0: + new_time = menu_day + rot1_clicks; + if(new_time > 31) + menu_day = 1; + else if(new_time < 1) + menu_day = 31; + else + menu_day = new_time; + menu_correct_date(); + break; + case 1: + new_time = menu_hour + rot1_clicks; + if(new_time > 23) + menu_hour = 0; + else if(new_time < 0) + menu_hour = 23; + else + menu_hour = new_time; + break; + } + + menu_time_changed = true; } - //brightness rot - if(rot3_clicks != 0) { - int16_t new_brightness = brightness + rot3_clicks * 8; - brightness = new_brightness > 248 ? 248 : new_brightness < 16 ? 16 : new_brightness; - display_overlay(OVERLAY_BRIGHTNESS); + display_change = true; + rot1_clicks = 0; + } + + //diashow speed rot + if(rot2_clicks != 0) { + if(menu == MENU_NONE) { + diashow_enabled = true; + int32_t new_diashow_time = diashow_time - rot2_clicks * 1000; + diashow_time = new_diashow_time > 60000 ? 60000 : new_diashow_time < 1000 ? 1000 : new_diashow_time; + ms_diashow = ms_current + diashow_time; + display_overlay(OVERLAY_DIASHOW_SPEED); preferences.begin(PREFERENCES_NAMESPACE); - preferences.putShort("brightness", brightness); + preferences.putUInt("diashow_time", diashow_time); + preferences.putBool("diashow", diashow_enabled); preferences.end(); + } else if(menu == MENU_DATETIME) { + if(!menu_time_changed) + menu_copy_time(); + + int8_t new_time; + + switch(menu_selection) { + case 0: + new_time = menu_year + rot2_clicks; + if(new_time > 99) + menu_year = 0; + else if(new_time < 0) + menu_year = 99; + else + menu_year = new_time; + menu_correct_date(); + break; + case 1: + new_time = menu_second + rot2_clicks; + if(new_time > 59) + menu_second = 0; + else if(new_time < 0) + menu_second = 59; + else + menu_second = new_time; + break; + } - rot3_clicks = 0; + menu_time_changed = true; } - //next button - if(btn1_pressed) { + display_change = true; + rot2_clicks = 0; + } + + //animation speed rot + if(rot3_clicks != 0) { + if(menu == MENU_NONE) { + animation_enabled = true; + int16_t new_animation_time = animation_time - rot3_clicks * 20; + animation_time = new_animation_time > 500 ? 500 : new_animation_time < 20 ? 20 : new_animation_time; + ms_animation = ms_current + animation_time; + display_overlay(OVERLAY_ANIMATION_SPEED); + + preferences.begin(PREFERENCES_NAMESPACE); + preferences.putUInt("animation_time", animation_time); + preferences.putBool("animation", animation_enabled); + preferences.end(); + } else if(menu == MENU_DATETIME) { + if(!menu_time_changed) + menu_copy_time(); + + int8_t new_time; + switch(menu_selection) { + case 0: + new_time = menu_month + rot3_clicks; + if(new_time > 12) + menu_month = 1; + else if(new_time < 1) + menu_month = 12; + else + menu_month = new_time; + menu_correct_date(); + break; + case 1: + new_time = menu_minute + rot3_clicks; + if(new_time > 59) + menu_minute = 0; + else if(new_time < 0) + menu_minute = 59; + else + menu_minute = new_time; + break; + } + + menu_time_changed = true; + } + + display_change = true; + rot3_clicks = 0; + } + + //next button + if(btn1_pressed) { + if(menu == MENU_NONE) { preferences.begin(PREFERENCES_NAMESPACE); if(current_mode == MODE_SOCIALS) { socials_channel_current = socials_channel_current + 1 >= socials_channels.size() ? 0 : socials_channel_current + 1; - preferences.putInt("current_social", socials_channel_current); + preferences.putUInt("current_social", socials_channel_current); } else if(current_mode == MODE_CLOCK) { current_clock_mode = static_cast((current_clock_mode + 1) % CLOCK_TYPE_NUMBER); - preferences.putInt("clock_mode", current_clock_mode); + preferences.putUInt("clock_mode", current_clock_mode); } else if(current_mode == MODE_IMAGES) { if(sd_connected() && image_index.size() > 0) { if(++selected_image >= image_index.size()) @@ -1450,16 +2689,25 @@ void loop() { } else { image_loaded = false; } - preferences.putInt("selected_image", selected_image); + preferences.putUInt("selected_image", selected_image); } preferences.end(); - display_change = true; - - btn1_pressed = false; + } else { + menu_selection++; + if((menu == MENU_OVERVIEW && menu_selection > 2) + || (menu == MENU_CLOCK && menu_selection > 3) + || (menu == MENU_DATETIME && menu_selection > 1) + || (menu == MENU_WIFI && menu_selection > 3)) + menu_selection = 0; } - //mode button - if(btn2_pressed) { + display_change = true; + btn1_pressed = false; + } + + //mode button + if(btn2_pressed) { + if(menu == MENU_NONE) { current_mode = static_cast((current_mode + 1) % DISPLAY_MODE_NUMBER); if(current_mode == MODE_SOCIALS && !wifi_connect) current_mode = static_cast((current_mode + 1) % DISPLAY_MODE_NUMBER); @@ -1472,82 +2720,236 @@ void loop() { } else { image_loaded = false; } + } else { + animation.clear(); } - display_overlay(OVERLAY_TEXT, current_mode == MODE_IMAGES ? strdup("Pictures") : current_mode == MODE_CLOCK ? strdup("Clock") : strdup("Socials")); + display_overlay(OVERLAY_TEXT, current_mode == MODE_IMAGES ? "Pictures" : current_mode == MODE_CLOCK ? "Clock" : "Socials"); preferences.begin(PREFERENCES_NAMESPACE); - preferences.putInt("current_mode", static_cast(current_mode)); + preferences.putUInt("current_mode", static_cast(current_mode)); + preferences.end(); + } else if(menu == MENU_OVERVIEW) { + switch(menu_selection) { + case 0: + menu = MENU_CLOCK; + break; + case 1: + menu = MENU_DATETIME; + break; + case 2: + menu = MENU_WIFI; + break; + } + menu_selection = 0; + } else if(menu == MENU_CLOCK) { + preferences.begin(PREFERENCES_NAMESPACE); + switch(menu_selection) { + case 0: + clock_seconds = !clock_seconds; + preferences.putBool("clock_seconds", clock_seconds); + break; + case 1: + clock_year = !clock_year; + preferences.putBool("clock_year", clock_year); + break; + case 2: + clock_blink = !clock_blink; + preferences.putBool("clock_blink", clock_blink); + break; + case 3: + time_format24 = !time_format24; + preferences.putBool("time_format24", time_format24); + break; + } + preferences.end(); + } else if(menu == MENU_DATETIME) { + menu_update_time(); + } else if(menu == MENU_WIFI) { + preferences.begin(PREFERENCES_NAMESPACE); + switch(menu_selection) { + case 0: + wifi_connect = !wifi_connect; + preferences.putBool("wifi_connect", wifi_connect); + wifi_setup(); + break; + case 1: + menu = MENU_WIFI_CONNECT; + break; + case 2: + wifi_host = !wifi_host; + preferences.putBool("wifi_host", wifi_host); + wifi_setup(); + break; + case 3: + menu = MENU_WIFI_HOST; + break; + } preferences.end(); - - btn2_pressed = false; } + + display_change = true; + btn2_pressed = false; + } - //menu button - if(ms_btn3_pressed != 0 && ms_btn3_pressed < ms_current) { - ms_btn3_pressed = 0; + //menu button + if(ms_btn3_pressed != 0 && ms_btn3_pressed < ms_current) { + ms_btn3_pressed = 0; - preferences.begin(PREFERENCES_NAMESPACE, false); - //TODO preferences.clear(); - preferences.end(); - requested_restart = true; - } else if(btn3_released) { - //approve api key for server if requested + preferences.begin(PREFERENCES_NAMESPACE, false); + preferences.clear(); + preferences.end(); + ms_requested_restart = ms_current; + } else if(btn3_released) { + if(menu == MENU_NONE) { + //approve api key for server if requested else open meu if(ms_api_key_request > ms_current) { ms_api_key_approve = ms_current + 5000; ms_api_key_request = 0; + } else { + menu = MENU_OVERVIEW; } - - btn3_released = false; + } else if(menu == MENU_WIFI || menu == MENU_CLOCK) { + menu = MENU_OVERVIEW; + } else if(menu == MENU_DATETIME) { + menu_update_time(); + menu = MENU_OVERVIEW; + } else if(menu == MENU_WIFI_CONNECT || menu == MENU_WIFI_HOST) { + menu = MENU_WIFI; + } else { + menu = MENU_NONE; } + + ms_animation = ms_current + animation_time; + ms_diashow = ms_current + diashow_time; + menu_selection = 0; + display_change = true; + btn3_released = false; + } + + - //rot1 button - if(rot1_pressed) { - //TODO + //brightness button + if(rot1_pressed) { + rot1_pressed = false; + } + + //diashow button + if(rot2_pressed) { + if(menu == MENU_NONE) { + diashow_enabled = !diashow_enabled; + ms_diashow = ms_current + diashow_time; + preferences.begin(PREFERENCES_NAMESPACE, false); + preferences.putBool("diashow", diashow_enabled); + preferences.end(); - rot1_pressed = false; - } + display_overlay(OVERLAY_TEXT, diashow_enabled ? "Diashow ON" : "Diashow OFF"); + } + + rot2_pressed = false; + } - //rot2 button - if(rot2_pressed) { - //TODO + //animation button + if(rot3_pressed) { + if(menu == MENU_NONE) { + animation_enabled = !animation_enabled; + ms_animation = ms_current + animation_time; + preferences.begin(PREFERENCES_NAMESPACE, false); + preferences.putBool("animation", animation_enabled); + preferences.end(); - rot2_pressed = false; + display_overlay(OVERLAY_TEXT, animation_enabled ? "Animation ON" : "Animation OFF"); } - //rot3 button - if(rot3_pressed) { - //TODO - - rot3_pressed = false; + rot3_pressed = false; + } + + + //Check loading animation + if(ms_loading != 0 && ms_loading < ms_current) { + loading_step++; + if(loading_step >= boot_sequence.size()) { + loading_step = 0; + loading_cycle = !loading_cycle; } + ms_loading = ms_current + 30; + display_change = true; + } + + //check overlay time + if(ms_overlay != 0 && ms_overlay < ms_current) { + overlay = OVERLAY_NONE; + ms_overlay = 0; + display_change = true; + } + + //Clock refresh cycle + if((current_mode == MODE_CLOCK || menu == MENU_DATETIME) && ms_clock < ms_current) { + ms_clock = ms_current + 100; + display_change = true; + } + //Wifi scan + if(ms_wifi_scan_requested != 0 && ms_wifi_scan_requested < ms_current && !wifi_scan_pending) { + ms_wifi_scan_requested = 0; - //check overlay time - if(ms_overlay != 0 && ms_overlay < ms_current) { - overlay = OVERLAY_NONE; - ms_overlay = 0; - display_change = true; + if(ms_wifi_scan_last == 0 || ms_wifi_scan_last + 90000 < ms_current) { + wifi_scan_pending = true; + + wifi_setup_complete = true; + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + WiFi.scanNetworks(true); + ms_wifi_scan_last = millis(); } + } + if(wifi_scan_pending) { + int networks = WiFi.scanComplete(); + if(networks >= 0) { + //delete old networks + for(int i = 0; i < available_networks.size(); i++) { + free(available_networks[i].ssid); + } + available_networks.clear(); + + //save new networks + for(int i = 0; i < networks; i++) { + available_network network = { + strdup(WiFi.SSID(i).c_str()), + WiFi.RSSI(i), + WiFi.encryptionType(i) + }; + available_networks.emplace_back(network); + } + WiFi.scanDelete(); + wifi_setup(); - //Clock refresh cycle - if(current_mode == MODE_CLOCK && ms_clock < ms_current) { - ms_clock = ms_current + 250; - display_change = true; + //clear state + wifi_scan_pending = false; + ms_wifi_scan_requested = 0; } } - //Wifi routine + //Wifi connection routine if(!wifi_setup_complete && ms_wifi_routine <= ms_current) { ms_wifi_routine = ms_current + 5000; - if(WiFi.waitForConnectResult(100) == WL_CONNECTED) { + if(WiFi.waitForConnectResult(10) == WL_CONNECTED) { wifi_setup_complete = true; wifi_on_connected(); } else if(ms_wifi_reconnect <= ms_current) { - wifi_setup(); + wifi_setup(true); } } + if(ms_wifi_restart != 0 && ms_wifi_restart <= ms_current) { + ms_wifi_restart = 0; + wifi_setup(); + } + + //DNS Routine + if(wifi_host) { + dns_server.processNextRequest(); + } //RTC adjustment after message received if(rtc_ext_adjust && ms_rtc_ext_adjust <= ms_current) { @@ -1555,45 +2957,32 @@ void loop() { } //Execute reset from api request - if(requested_restart) { - ESP.restart(); + if(ms_requested_restart != 0 && ms_requested_restart >= ms_current) { + panel->stopDMAoutput(); + restart(); } //Routine for async http - if(socials_response_check != 0 && socials_response_check <= ms_current) { - if(http_socials.readyState() == readyStateDone) { - socials_response_check = 0; - on_socials_response(); - } else { - socials_response_check += 250; + if(menu == MENU_NONE && wifi_connect && wifi_setup_complete) { + if(socials_response_check != 0 && socials_response_check <= ms_current) { + if(http_socials.readyState() == readyStateDone) { + socials_response_check = 0; + on_socials_response(); + } else { + socials_response_check += 250; + } + } else if(current_mode == MODE_SOCIALS && ms_socials_request != 0 && ms_socials_request <= ms_current) { + ms_socials_request = ms_current + SOCIAL_REFRESH_INTERVAL; + socials_refresh(); } - } else if(current_mode == MODE_SOCIALS && ms_socials_request != 0 && ms_socials_request <= ms_current) { - ms_socials_request = ms_current + SOCIAL_REFRESH_INTERVAL; - socials_refresh(); } + //Routine for file operations (moves/renames/deletes) + sd_operate_files(); + //refresh display if needed if(display_change) { display_change = false; display_current(); } - - //TODO remove tests - if(ms_test <= ms_current) { - ms_test = ms_current + 1000; - - // Serial.println(WiFi.localIP().toString()); - // Serial.println(rtc_int.getTime()); - // Serial.println(rtc_ext.now().timestamp()); - // Serial.println(api_key); - // Serial.println(http_socials.readyState()); - // Serial.println(socials_response.size()); - // Serial.println(brightness); - - // uint32_t heap = ESP.getFreeHeap(); - // if(heap != lastHeap) { - // Serial.println(heap); - // lastHeap = heap; - // } - } } \ No newline at end of file diff --git a/pixelart-controller/src/main.h.example b/pixelart-controller/src/main.h.example index 2ecac80..cc79a73 100644 --- a/pixelart-controller/src/main.h.example +++ b/pixelart-controller/src/main.h.example @@ -19,18 +19,18 @@ #define GPIO_LEDPANEL_OE 45 //Buttons -#define GPIO_BTN1 14 -#define GPIO_BTN2 16 -#define GPIO_BTN3 13 +#define GPIO_BTN1 8 +#define GPIO_BTN2 3 +#define GPIO_BTN3 10 #define GPIO_ROT1_BTN 12 -#define GPIO_ROT1_B 11 -#define GPIO_ROT1_A 10 +#define GPIO_ROT1_B 14 +#define GPIO_ROT1_A 13 #define GPIO_ROT2_BTN 9 #define GPIO_ROT2_B 46 -#define GPIO_ROT2_A 3 -#define GPIO_ROT3_BTN 8 -#define GPIO_ROT3_B 18 -#define GPIO_ROT3_A 17 +#define GPIO_ROT2_A 11 +#define GPIO_ROT3_BTN 18 +#define GPIO_ROT3_B 17 +#define GPIO_ROT3_A 16 //RTC #define GPIO_RTC_SCL 7 @@ -60,12 +60,12 @@ * Wifi * **********/ -#define WIFI_CONNECT_DEFAULT true // Connect to existing network +#define WIFI_CONNECT_DEFAULT false // Connect to existing network #define WIFI_HOST_DEFAULT false // Host own wifi network // Wifi information for hosting own network -#define WIFI_AP_SSID_DEFAULT "DerEffi's Pixelart" -#define WIFI_AP_PASSWORD_DEFAULT "" +#define WIFI_AP_SSID_DEFAULT "DerEffis Pixelart" +#define WIFI_AP_PASSWORD_DEFAULT "00000000" // Wifi information for connection to existing network #define WIFI_SSID_DEFAULT "" @@ -79,9 +79,9 @@ * Misc * **********/ -#define VERSION "0.0.1" +#define VERSION "1.0.0" #define PREFERENCES_NAMESPACE "pixelart" -#define SOCIAL_REFRESH_INTERVAL 25000 +#define SOCIAL_REFRESH_INTERVAL 31000 #define TIME_UPDATE_AUTO true #define TIME_FORMAT24 true @@ -90,4 +90,4 @@ #define SOCIALS_API_KEY "" #define SOCIALS_API_SERVER "" -#define SOCIALS_REQUEST "[{\"t\":\"t\",\"c\":\"dereffi\",\"d\":\"@DerEffi\"},{\"t\":\"y\",\"c\":\"dereffi\",\"d\":\"@DerEffi\"}]" \ No newline at end of file +#define SOCIALS_REQUEST "[{\"t\":\"t\",\"c\":\"dereffi\",\"d\":\"@DerEffi\"},{\"t\":\"y\",\"c\":\"jadereffi\",\"d\":\"@JaDerEffi\"},{\"t\":\"i\",\"c\":\"jadereffi\",\"d\":\"@JaDerEffi\"}]" \ No newline at end of file diff --git a/pixelart-controller/version.json b/pixelart-controller/version.json new file mode 100644 index 0000000..9fbefb3 --- /dev/null +++ b/pixelart-controller/version.json @@ -0,0 +1,14 @@ +{ + "name": "DerEffi's Pixelart", + "author": { + "name": "DerEffi", + "email": "info@dereffi.de", + "url": "https://dereffi.de" + }, + "version": "1.0.0", + "type": "firmware", + "files": [ + "firmware.bin" + ] + } + \ No newline at end of file diff --git a/pixelart-interface/.env b/pixelart-interface/.env index adf7e1a..b3dad95 100644 --- a/pixelart-interface/.env +++ b/pixelart-interface/.env @@ -1,3 +1,4 @@ GENERATE_SOURCEMAP=false REACT_APP_ENVIRONMENT=web -REACT_APP_VERSION=0.1.0 \ No newline at end of file +REACT_APP_VERSION=1.0.0 +REACT_APP_UPDATE_SERVER=https://update.pixelart.dereffi.de \ No newline at end of file diff --git a/pixelart-interface/README.md b/pixelart-interface/README.md index b87cb00..0ff809a 100644 --- a/pixelart-interface/README.md +++ b/pixelart-interface/README.md @@ -1,8 +1,24 @@ -# Getting Started with Create React App +#

Pixelart - Interface

-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +## About -## Available Scripts +This React App is used to remote control the device controller by browser. + +## Installing + +1. Install [NodeJS](https://nodejs.org) on your system. This app waas created with version 17. Although different versions shouldn't be a problem, I can't guarantee that it will work. If you have any trouble try switching to version 17. +1. Install dependencies by running `npm install` in the project directory. + +## Customization + +You can change the settings in the `.env` file: +- `REACT_APP_ENVIRONMENT`: + - `web` allows you to input a device ip. This is for hosted scenario like on my project page [pixelart.dereffi.de](http://pixelart.dereffi.de). + - `device` will use the current domain and assumes it is hosted on the device. By using this option you can copy the build files to the `webinterface` directory on your sd-card +- `REACT_APP_VERSION`: Will compare to the Version present on the update server to determine if there is an update available +- `REACT_APP_UPDATE_SERVER`: You can edit the update server url if you want to use your own update server. By executing `scripts/versionInformation.ps1` you will get a `version.json` file to be placed inside `www.your-update.server/webinterface/` as a file list and version metadata. + +## Running the App In the project directory, you can run: @@ -14,11 +30,6 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - ### `npm run build` Builds the app for production to the `build` folder.\ @@ -29,18 +40,6 @@ Your app is ready to be deployed! See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +## Usage -To learn React, check out the [React documentation](https://reactjs.org/). +Once you run the App you can find more details on how to use on the startpage or under [http://pixelart.dereffi.de](http://pixelart.dereffi.de). \ No newline at end of file diff --git a/pixelart-interface/package-lock.json b/pixelart-interface/package-lock.json index ee45314..71e94ad 100644 --- a/pixelart-interface/package-lock.json +++ b/pixelart-interface/package-lock.json @@ -13,30 +13,39 @@ "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.11", + "@types/omggif": "^1.0.3", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", "@types/react-icons": "^3.0.0", "axios": "^1.3.4", "buffer": "^6.0.3", + "chart.js": "^4.2.1", + "gifuct-js": "^2.1.2", "jszip": "^3.10.1", "moment": "^2.29.4", + "moment-duration-format": "^2.3.2", "primeicons": "^6.0.1", "primereact": "^9.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-fiber-keep-alive": "^0.7.1", "react-icons": "^4.7.1", "react-router-dom": "^6.8.1", "react-router-hash-link": "^2.4.3", "react-scripts": "5.0.1", + "semver": "^7.3.8", "ts-md5": "^1.3.1", "typescript": "^4.9.5", + "uuid": "^9.0.0", "web-vitals": "^2.1.4" }, "devDependencies": { + "@types/chart.js": "^2.9.37", + "@types/chartjs": "^0.0.31", + "@types/moment-duration-format": "^2.2.3", "@types/node-sass": "^4.11.3", "@types/react-router-dom": "^5.3.3", "@types/react-router-hash-link": "^2.4.5", + "@types/uuid": "^9.0.1", "node-sass": "^7.0.3" } }, @@ -2973,6 +2982,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -3720,6 +3734,22 @@ "@types/node": "*" } }, + "node_modules/@types/chart.js": { + "version": "2.9.37", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.37.tgz", + "integrity": "sha512-9bosRfHhkXxKYfrw94EmyDQcdjMaQPkU1fH2tDxu8DWXxf1mjzWQAV4laJF51ZbC2ycYwNDvIm1rGez8Bug0vg==", + "dev": true, + "dependencies": { + "moment": "^2.10.2" + } + }, + "node_modules/@types/chartjs": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/chartjs/-/chartjs-0.0.31.tgz", + "integrity": "sha512-eF1AgrIO0qP9KJYwM2dTKL2pczTjiLa0hfmZdQfKiqFq5WIvFTKPn1gkgADitF5sT4byEyq4EFdMVvuPIk6zeQ==", + "deprecated": "'@types/chartjs' is now '@types/chart.js'", + "dev": true + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -3859,6 +3889,15 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "devOptional": true }, + "node_modules/@types/moment-duration-format": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/moment-duration-format/-/moment-duration-format-2.2.3.tgz", + "integrity": "sha512-NQrnFOX1PTvHY8OH1aLoZntAkc2ad/1Cdl31UWIEaqBpDJ/m5SwfFBtFaX7TBLavASeLxG8DL7rm3NHKCLduaA==", + "dev": true, + "dependencies": { + "moment": ">=2.14.0" + } + }, "node_modules/@types/node": { "version": "16.18.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", @@ -3879,6 +3918,11 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "devOptional": true }, + "node_modules/@types/omggif": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/omggif/-/omggif-1.0.3.tgz", + "integrity": "sha512-u6WxvVuUkf4S+93ovKXBWSKaxlkCo63l6wBEgphBiIlXEsUcNFrl0nE5NbQA5u2ffvk3xc+3aN/DfDPnmrKRlg==" + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4042,6 +4086,12 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "node_modules/@types/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -5733,6 +5783,17 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz", + "integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": "^7.0.0" + } + }, "node_modules/check-types": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", @@ -8760,6 +8821,14 @@ "assert-plus": "^1.0.0" } }, + "node_modules/gifuct-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz", + "integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==", + "dependencies": { + "js-binary-schema-parser": "^2.0.3" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12025,6 +12094,11 @@ "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "devOptional": true }, + "node_modules/js-binary-schema-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", + "integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==" + }, "node_modules/js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", @@ -12848,6 +12922,11 @@ "node": "*" } }, + "node_modules/moment-duration-format": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz", + "integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15428,15 +15507,6 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, - "node_modules/react-fiber-keep-alive": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/react-fiber-keep-alive/-/react-fiber-keep-alive-0.7.1.tgz", - "integrity": "sha512-Osh6f0KWg6qjOZBdneg0v3WgttT/DS4aWehJXF46hd74LPVWGD73hoDAAZXv/UTh0XZQS8gNr2FNijsS6A50BA==", - "peerDependencies": { - "react": "^16.8 || 17 || 18", - "react-dom": "^16.8 || 17 || 18" - } - }, "node_modules/react-icons": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz", @@ -16674,6 +16744,14 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -17950,9 +18028,9 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } @@ -20945,6 +21023,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -21448,6 +21531,21 @@ "@types/node": "*" } }, + "@types/chart.js": { + "version": "2.9.37", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.37.tgz", + "integrity": "sha512-9bosRfHhkXxKYfrw94EmyDQcdjMaQPkU1fH2tDxu8DWXxf1mjzWQAV4laJF51ZbC2ycYwNDvIm1rGez8Bug0vg==", + "dev": true, + "requires": { + "moment": "^2.10.2" + } + }, + "@types/chartjs": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/chartjs/-/chartjs-0.0.31.tgz", + "integrity": "sha512-eF1AgrIO0qP9KJYwM2dTKL2pczTjiLa0hfmZdQfKiqFq5WIvFTKPn1gkgADitF5sT4byEyq4EFdMVvuPIk6zeQ==", + "dev": true + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -21587,6 +21685,15 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "devOptional": true }, + "@types/moment-duration-format": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/moment-duration-format/-/moment-duration-format-2.2.3.tgz", + "integrity": "sha512-NQrnFOX1PTvHY8OH1aLoZntAkc2ad/1Cdl31UWIEaqBpDJ/m5SwfFBtFaX7TBLavASeLxG8DL7rm3NHKCLduaA==", + "dev": true, + "requires": { + "moment": ">=2.14.0" + } + }, "@types/node": { "version": "16.18.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", @@ -21607,6 +21714,11 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "devOptional": true }, + "@types/omggif": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/omggif/-/omggif-1.0.3.tgz", + "integrity": "sha512-u6WxvVuUkf4S+93ovKXBWSKaxlkCo63l6wBEgphBiIlXEsUcNFrl0nE5NbQA5u2ffvk3xc+3aN/DfDPnmrKRlg==" + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -21769,6 +21881,12 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@types/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "dev": true + }, "@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -23011,6 +23129,14 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "chart.js": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz", + "integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==", + "requires": { + "@kurkle/color": "^0.3.0" + } + }, "check-types": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", @@ -25226,6 +25352,14 @@ "assert-plus": "^1.0.0" } }, + "gifuct-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz", + "integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==", + "requires": { + "js-binary-schema-parser": "^2.0.3" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -27584,6 +27718,11 @@ "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "devOptional": true }, + "js-binary-schema-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", + "integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==" + }, "js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", @@ -28219,6 +28358,11 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, + "moment-duration-format": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz", + "integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -29913,12 +30057,6 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, - "react-fiber-keep-alive": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/react-fiber-keep-alive/-/react-fiber-keep-alive-0.7.1.tgz", - "integrity": "sha512-Osh6f0KWg6qjOZBdneg0v3WgttT/DS4aWehJXF46hd74LPVWGD73hoDAAZXv/UTh0XZQS8gNr2FNijsS6A50BA==", - "requires": {} - }, "react-icons": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz", @@ -30833,6 +30971,13 @@ "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } } }, "socks": { @@ -31810,9 +31955,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" }, "v8-to-istanbul": { "version": "8.1.1", diff --git a/pixelart-interface/package.json b/pixelart-interface/package.json index 0971418..fcde338 100644 --- a/pixelart-interface/package.json +++ b/pixelart-interface/package.json @@ -13,24 +13,29 @@ "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.11", + "@types/omggif": "^1.0.3", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", "@types/react-icons": "^3.0.0", "axios": "^1.3.4", "buffer": "^6.0.3", + "chart.js": "^4.2.1", + "gifuct-js": "^2.1.2", "jszip": "^3.10.1", "moment": "^2.29.4", + "moment-duration-format": "^2.3.2", "primeicons": "^6.0.1", "primereact": "^9.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-fiber-keep-alive": "^0.7.1", "react-icons": "^4.7.1", "react-router-dom": "^6.8.1", "react-router-hash-link": "^2.4.3", "react-scripts": "5.0.1", + "semver": "^7.3.8", "ts-md5": "^1.3.1", "typescript": "^4.9.5", + "uuid": "^9.0.0", "web-vitals": "^2.1.4" }, "scripts": { @@ -58,9 +63,13 @@ ] }, "devDependencies": { + "@types/chart.js": "^2.9.37", + "@types/chartjs": "^0.0.31", + "@types/moment-duration-format": "^2.2.3", "@types/node-sass": "^4.11.3", "@types/react-router-dom": "^5.3.3", "@types/react-router-hash-link": "^2.4.5", + "@types/uuid": "^9.0.1", "node-sass": "^7.0.3" } } diff --git a/pixelart-interface/public/.htaccess b/pixelart-interface/public/.htaccess new file mode 100644 index 0000000..9d0b3f5 --- /dev/null +++ b/pixelart-interface/public/.htaccess @@ -0,0 +1,25 @@ + + + RewriteEngine On + RewriteBase / + RewriteRule ^index\.html$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-l + RewriteRule . /index.html [L] + + + + + ExpiresActive On + ExpiresDefault "access plus 600 seconds" + ExpiresByType text/html "access plus 600 seconds" + ExpiresByType image/x-icon "access plus 86400 seconds" + ExpiresByType image/gif "access plus 86400 seconds" + ExpiresByType image/jpeg "access plus 86400 seconds" + ExpiresByType image/png "access plus 86400 seconds" + ExpiresByType image/webp "access plus 86400 seconds" + ExpiresByType text/css "access plus 86400 seconds" + ExpiresByType text/javascript "access plus 86400 seconds" + ExpiresByType application/x-javascript "access plus 86400 seconds" + \ No newline at end of file diff --git a/pixelart-interface/public/assets/backside.webp b/pixelart-interface/public/assets/backside.webp new file mode 100644 index 0000000..b2ac38e Binary files /dev/null and b/pixelart-interface/public/assets/backside.webp differ diff --git a/pixelart-interface/public/assets/clock.webp b/pixelart-interface/public/assets/clock.webp new file mode 100644 index 0000000..27c5536 Binary files /dev/null and b/pixelart-interface/public/assets/clock.webp differ diff --git a/pixelart-interface/public/assets/overview.webp b/pixelart-interface/public/assets/overview.webp new file mode 100644 index 0000000..c4cac2f Binary files /dev/null and b/pixelart-interface/public/assets/overview.webp differ diff --git a/pixelart-interface/public/assets/pictures.webp b/pixelart-interface/public/assets/pictures.webp new file mode 100644 index 0000000..211c5dd Binary files /dev/null and b/pixelart-interface/public/assets/pictures.webp differ diff --git a/pixelart-interface/public/assets/socials.webp b/pixelart-interface/public/assets/socials.webp new file mode 100644 index 0000000..37fe0c1 Binary files /dev/null and b/pixelart-interface/public/assets/socials.webp differ diff --git a/pixelart-interface/scripts/versionInformation.ps1 b/pixelart-interface/scripts/versionInformation.ps1 index affab6f..a16778e 100644 --- a/pixelart-interface/scripts/versionInformation.ps1 +++ b/pixelart-interface/scripts/versionInformation.ps1 @@ -1,10 +1,12 @@ $basePath = Split-Path -Parent (Split-Path -Parent $PSCommandPath) $buildfolder = "$basePath\build" -$versionFile = "$buildfolder\interface.version.json" +$versionFile = "$buildfolder\version.json" +$templateFile = "$basePath\version.json" $envFile = "$basePath\.env" $env = @{} -if((Test-Path -Path $versionFile) -and (Test-Path -Path $envFile)) { +if((Test-Path -Path $templateFile) -and (Test-Path -Path $envFile)) { + Copy-Item $templateFile $versionFile Get-Content $envFile | foreach { $name, $value = $_.split('=') $env[$name] = $value @@ -12,9 +14,11 @@ if((Test-Path -Path $versionFile) -and (Test-Path -Path $envFile)) { cd $buildfolder $json = Get-Content $versionFile | ConvertFrom-Json $json.version = $env.REACT_APP_VERSION - $json.type = $env.REACT_APP_ENVIRONMENT - $json.files = get-childitem -file -path .\ -Recurse | Resolve-Path -Relative | % { ($_ -replace "\\", "/") -replace "\.\/", "/" } + $json.environment = $env.REACT_APP_ENVIRONMENT + $json.files = get-childitem -file -path .\ -Recurse | Resolve-Path -Relative | % { if(!$_.StartsWith(".\")) {$_ = ".\$($_)"}; "$(($_ -replace "\\", "/") -replace "\.\/", '')" } | ? { $_ -notlike "*.htaccess*" -and $_ -notlike "*robots.txt*" } $json | ConvertTo-Json -depth 32 | Out-File $versionFile } else { Write-Error "Version file in build folder does not exist" -} \ No newline at end of file +} + +cd $basePath \ No newline at end of file diff --git a/pixelart-interface/src/App.scss b/pixelart-interface/src/App.scss index dd646cb..2e3ed39 100644 --- a/pixelart-interface/src/App.scss +++ b/pixelart-interface/src/App.scss @@ -10,6 +10,30 @@ z-index: 0 !important; } +.p-dialog-header-icon, .p-dialog-header-icon:focus, .p-dialog-header-icon:active { + border: none !important; + box-shadow: none !important; +} + +.p-orderlist { + &-header { + text-align: center; + } + &-droppoint { + height: 1.25rem !important; + } + &-list { + overflow: initial !important; + height: auto !important; + min-height: auto !important; + max-height: none !important; + } +} + +.p-chip-text { + margin: 0 !important; +} + @@ -19,16 +43,12 @@ //Phones @media (max-width: 480px) { - body { - font-size: 16px; - } - .content { - width: 80%; + width: 90%; } .font-xxl { - font-size: 40px; + font-size: 65px; } .font-xl { @@ -50,20 +70,39 @@ #sidebar { width: calc(100vw - 4rem) !important; } + + .p-message-wrapper { + display: block !important; + text-align: center; + + & > * { + text-align: left; + } + } + + .device-image img { + max-width: 250px !important; + max-height: 250px !important; + } + + .home-table td { + display: block; + + &:first-child { + margin-top: 15px; + font-weight: bold; + } + } } //Tablets @media (min-width: 481px) and (max-width: 768px) { - body { - font-size: 17px; - } - .content { - width: 75%; + width: 85%; } .font-xxl { - font-size: 50px; + font-size: 75px; } .font-xl { @@ -81,10 +120,6 @@ //Laptops @media (min-width: 769px) and (max-width: 1279px) { - body { - font-size: 20px; - } - .content { width: 80%; } @@ -128,10 +163,6 @@ //Monitors @media (min-width: 1280px) { - body { - font-size: 20px; - } - .content { width: 70%; } @@ -186,15 +217,21 @@ } .warn { - color: #fbc02d; + color: #fbc02d !important; } -a { +a, .link { text-decoration: none; - color: var(--text-color); + color: var(--primary-color); + cursor: pointer; + + &:hover { + color: var(--primary-400); + } } body { + font-size: 1.1rem; background-color: var(--surface-ground); font-family: var(--font-family); color: var(--text-color); @@ -259,9 +296,13 @@ h1, h2, h3, h4, h5, .hl { margin-bottom: 1rem; } - &:not(:first-child) { + &:not(:first-child):not(.p-dialog-content) { margin-top: 3rem; } + + &.p-dialog-content { + margin-top: 1rem; + } } } @@ -399,8 +440,13 @@ img { flex-wrap: wrap; &-field { - width: 20rem !important; - max-width: 70vw !important; + width: 20rem; + max-width: 70vw; + + &:not(textarea) { + width: 20rem !important; + max-width: 70vw !important; + } } } @@ -493,6 +539,7 @@ img { margin-top: 2rem; position: relative; transition: margin-left .2s; + overflow: auto; } } @@ -597,24 +644,23 @@ img { } } -#image-generator { - &-image { - display: none; - object-fit: contain; - image-rendering: pixelated; - } - - &-canvas { - &-wrapper { - text-align: center; - } +.image-generator-image { + display: none; + object-fit: fill; + image-rendering: pixelated; +} - max-width: 100%; - width: 512px; - image-rendering: pixelated; - border: 1px solid var(--surface-d); - border-radius: var(--border-radius); +#image-generator-canvas { + &-wrapper { + text-align: center; + position: relative; } + + max-width: 100%; + width: 384px; + image-rendering: pixelated; + border: 1px solid var(--surface-d); + border-radius: var(--border-radius); } .image-generator { @@ -640,8 +686,306 @@ img { width: 100px; height: 100px; border-radius: var(--border-radius); - object-fit: contain; + object-fit: fill; display: block; + image-rendering: pixelated; + } + } + &-rendering, &-rendering-blocker { + width: 100%; + height: 100%; + position: absolute; + display: flex; + justify-content: center; + align-items: center; + color: var(--text-color); + } + + &-rendering-blocker { + opacity: .4; + background-color: var(--surface-ground); + } + + &-history { + background-color: var(--surface-a); + + &-header { + background-color: var(--surface-b) !important; + border: 1px solid var(--surface-d) !important; + } + + &-images { + display: flex; + flex-wrap: wrap; + gap: .8rem; + margin: .8rem; + } + + &-image { + display: inline-block; + margin: .8rem; + } + + &-item { + padding: 1rem; + } + + &-tooltip { + text-align: center; + padding: 4px; + + & i { + font-size: .9rem; + } + + & > div { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + } + } + } +} + +.socials { + &-edit { + background-color: var(--surface-a); + + &-header { + background-color: var(--surface-b) !important; + border: 1px solid var(--surface-d) !important; + } + + &-form { + text-align: center; + width: 100%; + + & > div { + margin: 2rem auto; + max-width: 100%; + width: 300px; + + & > * { + text-align: left; + width: 100%; + + &.socials-edit-label { + text-align: center; + margin: .5rem; + } + } + } + + & > div.socials-edit-submit > * { + justify-content: center; + } + } + } + + &-test { + background-color: var(--surface-a); + + &-header { + background-color: var(--surface-b) !important; + border: 1px solid var(--surface-d) !important; } + + &-items { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + margin: 1rem; + text-align: center; + } + + &-item { + display: inline-block; + margin: .4rem .8rem; + padding: .5rem 1rem; + cursor: pointer; + + &-icon { + font-size: 3rem; + } + + &:not(:hover) > * { + color: var(--text-color) !important; + } + } + } + + &-list { + &-item { + display: flex; + align-items: center; + gap: 1.5rem; + cursor: grab; + flex-wrap: nowrap; + + &-icon { + font-size: 1.75rem; + flex: 0 0 auto; + } + + &-input { + flex: 1 1 auto; + display: flex; + flex-wrap: wrap; + gap: .5rem; + align-items: center; + + &-icon { + font-size: 1.33rem; + flex: 0 0 auto; + } + } + } + } +} + +.update-table { + margin: auto; + + & td { + padding: .5rem 1rem; + text-align: right; + } +} + +.message-icon { + flex: 0 0 auto; + font-size: 2rem; + margin-right: 1rem; +} + +.wifi-network-item { + display: flex; + flex-wrap: nowrap; + gap: .5rem; + + &-icon { + flex: 0 0 auto; + font-size: 1.2rem; + } + + &-label { + margin: 0 15px; + } +} + +.chart { + margin: auto; + width: 850px; + max-width: 100%; +} + +.images { + &-edit { + background-color: var(--surface-a); + + &-header { + background-color: var(--surface-b) !important; + border: 1px solid var(--surface-d) !important; + } + + &-form { + text-align: center; + width: 100%; + + & > div { + margin: 2rem auto; + max-width: 100%; + width: 300px; + + & > * { + text-align: left; + width: 100%; + + &.images-edit-label { + text-align: center; + margin: .5rem; + } + } + } + + & > div.images-edit-submit > * { + justify-content: center; + } + } + } + + &-list { + &-item { + display: flex; + align-items: center; + gap: 1.5rem; + cursor: grab; + flex-wrap: nowrap; + + &-icon { + font-size: 1.75rem; + flex: 0 0 auto; + } + + &-input { + flex: 1 1 auto; + display: flex; + flex-wrap: wrap; + gap: .5rem; + align-items: center; + + &-icon { + font-size: 1.33rem; + flex: 0 0 auto; + } + } + } + } +} + +.home { + &-header { + display: flex; + flex-wrap: nowrap; + gap: .75rem; + font-size: 1.2rem; + + & svg { + font-size: 1.4rem; + } + } + + &-table { + margin-left: 10px; + border-collapse: collapse; + + & td { + padding: 5px 15px; + vertical-align: top; + + &:first-child { + white-space: nowrap; + } + } + } +} + +.error { + color: var(--primary-color) !important; +} + +.device-image { + margin: auto; + + & img { + display: block; + max-width: 90%; + max-height: 90%; + width: 350px; + height: 350px; + object-fit: contain; + margin: auto; + text-align: center; } } \ No newline at end of file diff --git a/pixelart-interface/src/App.tsx b/pixelart-interface/src/App.tsx index 74c7871..6a33c17 100644 --- a/pixelart-interface/src/App.tsx +++ b/pixelart-interface/src/App.tsx @@ -11,18 +11,23 @@ import PrimeReact from 'primereact/api'; import 'primereact/resources/primereact.min.css'; import { Tooltip } from 'primereact/tooltip'; import React from 'react'; -import { BiImages, BiRefresh, BiSun, BiTime, BiWifi } from 'react-icons/bi'; +import { BiDownload, BiImages, BiRefresh, BiSun, BiTime, BiWifi } from 'react-icons/bi'; import { BsGearFill, BsHouseFill } from 'react-icons/bs'; import { CgMenu } from 'react-icons/cg'; import { IoShareSocialSharp } from 'react-icons/io5'; import { TfiLayoutGrid4Alt } from 'react-icons/tfi'; import { GoSettings } from 'react-icons/go'; -import { Route, Routes } from 'react-router-dom'; +import { Location, Route, Routes } from 'react-router-dom'; import Settings from './components/pages/Settings'; import { Toast } from 'primereact/toast'; +import Downloads from './components/pages/Downloads'; PrimeReact.inputStyle = 'filled'; +export interface IAppProps { + location: Location; +} + interface IAppState { loaded: boolean; sidebar: boolean; @@ -30,12 +35,19 @@ interface IAppState { advanced: boolean; } -export default class App extends React.Component<{}, IAppState> { +export default class App extends React.Component { private dataService: DataService = new DataService(() => {this.setState({});}); private toast: Toast | null = null; + private windowWidth: number = 0; + private resizeEvent = () => { + if(window.screen.width < 769 && this.windowWidth >= 769 && this.windowWidth !== window.screen.width) + this.changeSidebar(false); - constructor(props: {}) { + this.windowWidth = window.screen.width; + }; + + constructor(props: IAppProps) { super(props); this.state = { @@ -51,6 +63,12 @@ export default class App extends React.Component<{}, IAppState> { if(window.screen.width < 769) this.changeSidebar(false); + + window.addEventListener("resize", this.resizeEvent); + } + + componentWillUnmount(): void { + window.removeEventListener("resize", this.resizeEvent); } public render(): JSX.Element { @@ -85,7 +103,8 @@ export default class App extends React.Component<{}, IAppState> { } /> } /> - } /> + } /> + } /> } /> @@ -129,9 +148,14 @@ const SidebarMenu: ISidebarItem[] = [ icon: }, { - label: 'Pictures', + label: 'Picture Generator', url: '/pictures', icon: + }, + { + label: 'Downloads', + url: '/downloads', + icon: } ] }, diff --git a/pixelart-interface/src/components/pages/Downloads.tsx b/pixelart-interface/src/components/pages/Downloads.tsx new file mode 100644 index 0000000..a0ace74 --- /dev/null +++ b/pixelart-interface/src/components/pages/Downloads.tsx @@ -0,0 +1,145 @@ +import DataService from '../../services/DataService'; +import React from 'react'; +import { Toast } from 'primereact/toast'; +import { Button } from 'primereact/button'; +import { Link } from 'react-router-dom'; +import axios from 'axios'; +import JSZip from 'jszip'; +import { VersionDetails } from '../../models/Version'; +import { Messages } from 'primereact/messages'; +import { BiInfoCircle } from 'react-icons/bi'; + +export interface IDownloadsComponentProps { + dataService: DataService; + toast: Toast | null; + advanced: boolean; +} + +interface IDownloadsComponentState { + downloading: boolean; +} + +export default class Downloads extends React.Component { + + private messages: Messages | null = null; + + constructor(props: IDownloadsComponentProps) { + super(props); + + this.state = { + downloading: false + } + } + + componentDidMount(): void { + if(this.messages) + this.messages.show({ + closable: false, + sticky: true, + severity: "info", + content: <> + +
+

+ Download the current version of the firmware, running the LED-Panel itself, and the webinterface for remote access from any device on your network. + After downloading you can place the files in the root directory of your sd card to start the update. +

+

+ You can also automatically update your device under 'Settings' > 'System' if you have established a connection with your device. +

+
+ , + }); + } + + public render() { + return( +
+ +
+ + + + + + + + + + + + + + +
Firmware{this.props.dataService.newestFirmware?.version || "-"}
Webinterface{this.props.dataService.newestWebinterface?.version || "-"}
+ + this.messages = el} style={{textAlign: 'left'}} /> +
+ +
+ ); + } + + private async downloadFiles(version?: VersionDetails) { + this.setState({ + downloading: true + }); + + try { + + if(!version) + throw new Error("No version information available"); + + //download files from update server + let files: {content: ArrayBuffer, path: string}[] = await Promise.all( + version.files.map(async path => + new Promise((resolve, reject) => { + console.dir(version); + axios({ + url: `${process.env.REACT_APP_UPDATE_SERVER}/${version.type}/${path}`, + method: "GET", + responseType: "arraybuffer" + }).then(resp => { + if(resp.status !== 200) + reject(); + + resolve({ + path: path, + content: resp.data + }); + }).catch((e) => { + reject(e); + }) + }) + ) + ); + + //create zip + let zip = new JSZip(); + zip.file("_place inside root folder on your sd card_", ""); + files.forEach(file => { + zip.file("webinterface/" + file.path, file.content); + }); + + zip.generateAsync({type: "blob"}).then((blob: Blob) => { + let file = document.createElement("a"); + file.href = window.URL.createObjectURL(blob); + file.download = `pixelart-${version.type}-${version.version}.zip`; + file.click(); + file.remove(); + }); + + } catch(e) { + if(this.props.toast) + this.props.toast.show({ + content: "There was an issue with your download", + severity: 'error', + closable: false + }); + } + + this.setState({ + downloading: false + }); + } +} \ No newline at end of file diff --git a/pixelart-interface/src/components/pages/Generator.tsx b/pixelart-interface/src/components/pages/Generator.tsx index b00727a..56a8c1d 100644 --- a/pixelart-interface/src/components/pages/Generator.tsx +++ b/pixelart-interface/src/components/pages/Generator.tsx @@ -6,10 +6,18 @@ import {Buffer} from 'buffer'; import { Tooltip } from 'primereact/tooltip'; import { Button } from 'primereact/button'; import {Md5} from 'ts-md5'; -import { c2dArray, rgb565, rgb888 } from '../../services/Helper'; +import { asyncTimeout, c2dArray, cAnimationArray, convertToFoldername, padLeft, rgb565, rgb888 } from '../../services/Helper'; import { InputText } from 'primereact/inputtext'; import { Status } from '../../models/Status'; import JSZip from 'jszip'; +import { Knob } from 'primereact/knob'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import { ICachedImage } from '../../models/Cache'; +import { Dialog } from 'primereact/dialog'; +import moment from 'moment'; +import { ConfirmPopup, confirmPopup } from 'primereact/confirmpopup'; +import { v4 as uuid } from 'uuid'; +import { parseGIF, decompressFrames } from 'gifuct-js' export interface IGeneratorComponentProps { dataService: DataService; @@ -18,23 +26,42 @@ export interface IGeneratorComponentProps { } interface IGeneratorComponentState { - canvasImage: string; + canvasImages: (HTMLImageElement | ImageData)[]; name: string; + frameTime: number; + frameInterval: NodeJS.Timer | null; + rendering: boolean; + cachedItems: ICachedImage[]; + viewHistory: boolean; + showTmpCopy: boolean; } export default class Generator extends React.Component { + private currentFrame: number = 0; + constructor(props: IGeneratorComponentProps) { super(props); this.state = { - canvasImage: "", - name: "" + canvasImages: [], + name: "", + frameTime: 100, + frameInterval: null, + rendering: false, + cachedItems: [], + viewHistory: false, + showTmpCopy: false } } - componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { - + componentDidMount(): void { + this.loadCache(); + } + + componentWillUnmount(): void { + if(this.state.frameInterval) + clearInterval(this.state.frameInterval); } public render() { @@ -42,9 +69,56 @@ export default class Generator extends React.Component
+ + this.setState({viewHistory: false})} + > +
+ + + + {this.state.cachedItems.map(image => { + return( +
+ +
+
{image.name}
+
{moment.unix(image.created).format("DD.MM.YYYY HH:mm")}
+
animated {image.animation.length > 0 ? : }
+
+
+
+ {image.name} +
+ +
+
+
+ ); + })} +
+
- Hidden container for generating canvas content + {this.state.rendering && +
+
+ +
Processing
+
+
+
+ }
@@ -52,24 +126,31 @@ export default class Generator extends React.Component
- xxx - + {padLeft((this.props.dataService.data.images?.imagePrefixMax || 0) + 1, 3)} - this.setState({name: e.target.value})} />
-
-
- - - + {this.props.advanced && +
+ + + +
+ } + +
+ + + - {chooseButton} - {uploadButton} - {cancelButton} +
+
+ {chooseButton} + {uploadButton} + {cancelButton} +
+
+
); }} + disabled={this.state.rendering} itemTemplate={(file: any, props) => { let md5name = Md5.hashStr(file.name); return(
{file.name}
- + -
) @@ -112,11 +199,11 @@ export default class Generator extends React.Component } progressBarTemplate={<>} - chooseOptions={{ icon: 'pi pi-fw pi-images', iconOnly: true, className: 'custom-choose-btn p-button-rounded p-button-info p-button-outlined', label: "Select" }} - uploadOptions={{ icon: 'pi pi-fw pi-upload', iconOnly: true, className: 'custom-upload-btn p-button-success p-button-rounded p-button-outlined', label: "Generate as Animation" }} + chooseOptions={{ icon: 'pi pi-fw pi-folder-open', iconOnly: true, className: 'custom-choose-btn p-button-rounded p-button-info p-button-outlined', label: "Select" }} + uploadOptions={{ icon: 'pi pi-fw pi-images', iconOnly: true, className: 'custom-upload-btn p-button-success p-button-rounded p-button-outlined', label: "Generate as Animation" }} cancelOptions={{ icon: 'pi pi-fw pi-times', iconOnly: true, className: 'custom-cancel-btn p-button-danger p-button-rounded p-button-outlined', label: "Clear" }} customUpload - uploadHandler={(e) => this.uploadAnimation(e.files)} + uploadHandler={(e) => this.uploadImages(e.files)} contentClassName="image-generator-upload" />
@@ -127,51 +214,389 @@ export default class Generator extends React.Component { + if(img instanceof HTMLImageElement) + img.remove(); + }); this.setState({ - canvasImage: "data:" + file.type + ";base64, " + base64image, - name: file.name.substring(0, file.name.lastIndexOf(".") !== -1 ? file.name.lastIndexOf(".") : file.name.length) - }, () => this.drawFromImage()); + canvasImages: [], + }, async () => { + if(files.length > 0) { + let images: (HTMLImageElement | ImageData)[] = []; + + for(let i: number = 0; i < files.length; i++) { + if(!files[i].type.startsWith("image/")) { + if(this.props.toast) + this.props.toast.show({ + content: "Please select proper image", + severity: 'error', + closable: false + }); + continue; + } else if (files[i].type === "image/gif") { + + let gif = parseGIF(await files[i].arrayBuffer()); + let frames = decompressFrames(gif, true); + + let ctx = this.newCanvas(gif.lsd.width, gif.lsd.height); + ctx.scale(1 / (gif.lsd.width / 64), 1 / (gif.lsd.height / 64)); + + frames.forEach(frame => { + let scaledCtx = this.newCanvas(gif.lsd.width, gif.lsd.height); + scaledCtx.putImageData(new ImageData(frame.patch, frame.dims.width, frame.dims.height), frame.dims.left, frame.dims.top); + ctx.drawImage(scaledCtx.canvas, 0, 0); + images.push(ctx.getImageData(0, 0, 64, 64)); + }); + + } else { + let byteArray = new Uint8Array(await files[i].arrayBuffer()); + let base64image = Buffer.from(byteArray).toString("base64"); + + let img = document.createElement("img"); + img.src = "data:" + files[i].type + ";base64, " + base64image; + img.width = 64; + img.height = 64; + img.alt = "Hidden container for generating canvas content"; + img.className = "image-generator-image"; + + images.push(img); + } + } + + this.setState({ + canvasImages: images, + name: files[0].name.substring(0, files[0].name.lastIndexOf(".") !== -1 ? files[0].name.lastIndexOf(".") : files[0].name.length) + }, () => setTimeout(() => this.drawFromImages(), 50)); + } else { + if(this.props.toast) + this.props.toast.show({ + content: "No image selected", + severity: 'error', + closable: false + }); + } + }); + } + + //start draw of first frame of prepared images and start animation if prepared image has one + private drawFromImages() { + this.currentFrame = 0; + this.drawFrame(); + if(this.state.frameInterval) + clearInterval(this.state.frameInterval); + this.setState({frameInterval: this.state.canvasImages.length > 1 ? setInterval(() => this.drawFrame(), this.state.frameTime) : null}); } - private async uploadAnimation(files: File[]) { - //TODO + //draw selected frame on selected canvas - stop animation if no animation data is present + private drawFrame(frame: number = ++this.currentFrame, canvas: CanvasRenderingContext2D = this.getCanvas()) { + if(this.state.canvasImages.length > 0) { + canvas.clearRect(0, 0, 64, 64); + frame = frame % this.state.canvasImages.length; + let frameData = this.state.canvasImages[frame]; + if(frameData instanceof HTMLImageElement) + canvas.drawImage(frameData, 0, 0, 64, 64); + else if(frameData instanceof ImageData) + canvas.putImageData(frameData, 0, 0); + this.currentFrame = frame; + + if(this.state.canvasImages.length === 1 && this.state.frameInterval) { + clearInterval(this.state.frameInterval); + this.setState({frameInterval: null}); + } + } else { + if(this.state.frameInterval) { + clearInterval(this.state.frameInterval); + this.setState({ + frameInterval: null + }); + } + if(this.props.toast) + this.props.toast.show({ + content: "No image to generate pixel art from", + severity: 'error', + closable: false + }); + } } - private drawFromImage() { - window.requestAnimationFrame(() => { - let image = document.getElementById("image-generator-image") as HTMLImageElement; - let ctx = this.getCanvas(); - ctx.drawImage(image, 0, 0, 64, 64); + //start / stop animation + private playAnimation() { + if(this.state.frameInterval) + clearInterval(this.state.frameInterval); + this.setState({ + frameInterval: this.state.frameInterval || this.state.canvasImages.length <= 1 ? null : setInterval(() => this.drawFrame(), this.state.frameTime) }); } - private downloadImage() { - let zip = new JSZip(); - zip.file("000 - " + this.state.name + "/image.pxart", this.getPixelArray()); - zip.file("place inside images folder on your device", ""); - zip.generateAsync({type: "blob"}).then((blob: Blob) => { - let file = document.createElement("a"); - file.href = window.URL.createObjectURL(blob); - file.download = this.state.name; - file.click(); + //set frametime for animation + private adjustSpeed(time: number) { + if(this.state.frameInterval) + clearInterval(this.state.frameInterval); + this.setState({ + frameTime: time, + frameInterval: this.state.frameInterval ? setInterval(() => this.drawFrame(), time) : null }); } - private uploadToDevice() { - //TODO + //calculate image and animation data for prepared images to download as zip + private async downloadImage() { + this.setState({rendering: true}); + if(this.state.frameInterval) + this.playAnimation(); + + let ctx: CanvasRenderingContext2D | null = this.newCanvas(); + if(ctx && this.state.canvasImages.length > 0) { + this.drawFrame(0, ctx); + + let preview: string = ctx.canvas.toDataURL(); + + let animation: number[][][] = []; + + if(this.state.canvasImages.length > 1) { + for(let i = 1; i < this.state.canvasImages.length; i++) { + animation.push(await this.getPixelChanges(i - 1, i)); + await asyncTimeout(1); + } + + //add loopback frame + animation.push(await this.getPixelChanges(this.state.canvasImages.length - 1, 0)); + await asyncTimeout(1); + } + + let image = this.getPixelArray(ctx); + let frames = animation.length > 0 ? this.getAnimationArray(animation) : []; + + let zip = new JSZip(); + let prefix = padLeft((this.props.dataService.data.images?.imagePrefixMax || 0) + 1, 3); + zip.file(prefix + " - " + this.state.name + "/image.pxart", image); + zip.file("_place inside images folder on your sd card_", ""); + if(animation.length) + zip.file(convertToFoldername(prefix + " - " + this.state.name) + "/animation.pxart", frames); + zip.generateAsync({type: "blob"}).then((blob: Blob) => { + let file = document.createElement("a"); + file.href = window.URL.createObjectURL(blob); + file.download = convertToFoldername(prefix + " - " + this.state.name) + ".zip"; + file.click(); + file.remove(); + }); + + ctx.canvas.remove(); + + this.cacheImage(this.state.name, image, animation, preview); + } else { + if(this.props.toast) + this.props.toast.show({ + content: "No image to generate pixel art from", + severity: 'error', + closable: false + }); + } + + if(this.state.canvasImages.length > 1) + this.playAnimation(); + + this.setState({rendering: false}); } - private copyCArray() { - navigator.clipboard.writeText(c2dArray(this.get2dPixelArray())); + //calculate image and animation data for prepared images to directly upload to connected device + private async uploadToDevice() { + this.setState({rendering: true}); + if(this.state.frameInterval) + this.playAnimation(); + + let ctx: CanvasRenderingContext2D | null = this.newCanvas(); + if(ctx && this.state.canvasImages.length > 0) { + this.drawFrame(0, ctx); + + let preview: string = ctx.canvas.toDataURL(); + + let animation: number[][][] = []; + + if(this.state.canvasImages.length > 1) { + for(let i = 1; i < this.state.canvasImages.length; i++) { + animation.push(await this.getPixelChanges(i - 1, i)); + await asyncTimeout(1); + } + + //add loopback frame + animation.push(await this.getPixelChanges(this.state.canvasImages.length - 1, 0)); + await asyncTimeout(1); + } + + let image = this.getPixelArray(ctx); + let frames = animation.length > 0 ? this.getAnimationArray(animation) : []; + + let files: FormData = new FormData(); + let prefix = padLeft((this.props.dataService.data.images?.imagePrefixMax || 0) + 1, 3); + let foldername: string = convertToFoldername(prefix + " - " + this.state.name); + + files.append(foldername + "/image.pxart", new Blob([Uint8Array.from(image)], {type: "octet/stream"}), uuid()); + if(animation.length) + files.append(foldername + "/animation.pxart", new Blob([Uint8Array.from(frames)], {type: "octet/stream"}), uuid()); + + //delete picture folder to be sure no artifacts from old pictures remain + await this.props.dataService.requestDevice("POST", "/api/images", { + imageOperations: [ + {src: foldername} + ] + }) + .then(async () => { + return await this.props.dataService.uploadFiles("images", files).then(async () => { + await asyncTimeout(1000); + this.props.dataService.refreshImages(); + if(this.props.toast) + this.props.toast.show({ + content: "Image uploaded to your device", + severity: 'success', + closable: false + }); + }).catch((e) => { + throw e; + }) + }).catch((e) => { + console.error(e); + if(this.props.toast) + this.props.toast.show({ + content: "There was a problem uploading the image to your device", + severity: 'error', + closable: false + }); + }); + + ctx.canvas.remove(); + + this.cacheImage(this.state.name, image, animation, preview); + } else { + if(this.props.toast) + this.props.toast.show({ + content: "No image to generate pixel art from", + severity: 'error', + closable: false + }); + } + + if(this.state.canvasImages.length > 1) + this.playAnimation(); + + this.setState({rendering: false}); } - private copyByteArray() { - navigator.clipboard.writeText(Buffer.from(this.getPixelArray()).toString("base64")); + //copy image and animation data from current preview for use in c/c++ + private async copyCArray(event: React.MouseEvent) { + if(this.state.frameInterval) { + this.setState({rendering: true}); + this.playAnimation(); + + let ctx: CanvasRenderingContext2D | null = this.newCanvas(); + if(ctx && this.state.canvasImages.length > 0) { + this.drawFrame(0, ctx); + + let animation: number[][][] = []; + + for(let i = 1; i < this.state.canvasImages.length; i++) { + animation.push(await this.getPixelChanges(i - 1, i)); + await asyncTimeout(1); + } + + //add loopback frame + animation.push(await this.getPixelChanges(this.state.canvasImages.length - 1, 0)); + await asyncTimeout(1); + ctx.canvas.remove(); + + let maxPixelChanges = 0; + animation.forEach(frame => maxPixelChanges = frame.length > maxPixelChanges ? frame.length : maxPixelChanges); + (window as any).tmpPixelData = "uint16_t image[64][64] = " + c2dArray(this.get2dPixelArray()) + ";\nuint16 animation[" + animation.length + "][" + maxPixelChanges + "][3] = " + cAnimationArray(animation) + ";"; + confirmPopup({ + target: event.target as HTMLElement, + message: "Your data is now ready", + acceptLabel: "Copy", + acceptIcon: "pi pi-copy", + accept: () => navigator.clipboard.writeText((window as any).tmpPixelData), + rejectLabel: "" + }); + } else { + if(this.props.toast) + this.props.toast.show({ + content: "No image to generate pixel art from", + severity: 'error', + closable: false + }); + } + + if(this.state.canvasImages.length > 1) + this.playAnimation(); + + this.setState({rendering: false}); + } else { + navigator.clipboard.writeText(c2dArray(this.get2dPixelArray())); + } } + //copy base64 binary data from current preview for hex editor + private async copyByteArray(event: React.MouseEvent) { + if(this.state.frameInterval) { + this.setState({rendering: true}); + if(this.state.frameInterval) + this.playAnimation(); + + let ctx: CanvasRenderingContext2D | null = this.newCanvas(); + if(ctx && this.state.canvasImages.length > 0) { + this.drawFrame(0, ctx); + + let animation: number[][][] = []; + + if(this.state.canvasImages.length > 1) { + for(let i = 1; i < this.state.canvasImages.length; i++) { + animation.push(await this.getPixelChanges(i - 1, i)); + await asyncTimeout(1); + } + + //add loopback frame + animation.push(await this.getPixelChanges(this.state.canvasImages.length - 1, 0)); + await asyncTimeout(1); + } + + let frames = animation.length > 0 ? this.getAnimationArray(animation) : []; + + ctx.canvas.remove(); + + (window as any).tmpImageData = Buffer.from(this.getPixelArray()).toString("base64"); + (window as any).tmpAnimationData = Buffer.from(frames).toString("base64"); + + confirmPopup({ + target: event.target as HTMLElement, + message: "Your data is now ready", + rejectLabel: "Copy Image", + rejectIcon: "pi pi-copy", + reject: () => navigator.clipboard.writeText((window as any).tmpImageData), + acceptLabel: frames.length > 0 ? "Copy Animation" : " ", + acceptIcon: frames.length > 0 ? "pi pi-copy" : "", + accept: () => navigator.clipboard.writeText((window as any).tmpAnimationData), + }); + } else { + if(this.props.toast) + this.props.toast.show({ + content: "No image to generate pixel art from", + severity: 'error', + closable: false + }); + } + + if(this.state.canvasImages.length > 1) + this.playAnimation(); + + this.setState({rendering: false}); + } else { + navigator.clipboard.writeText(Buffer.from(this.getPixelArray()).toString("base64")); + } + } + + //get preview canvas from dom private getCanvas(): CanvasRenderingContext2D { let canvas = document.getElementById("image-generator-canvas") as HTMLCanvasElement; if(canvas) { @@ -184,66 +609,211 @@ export default class Generator extends React.Component { + let ctx: CanvasRenderingContext2D | null = this.newCanvas(); + if(ctx && this.state.canvasImages.length > 0) { + this.drawFrame(base, ctx); + let baseData = this.get2dPixelArray(ctx); + this.drawFrame(frame, ctx); + let frameData = this.get2dPixelArray(ctx); + + let changes: number[][] = []; + for(var y = 0; y < 64; y++) { + for(var x = 0; x < 64; x++) { + if(baseData[y][x] !== frameData[y][x]) { + changes.push([ + x, + y, + frameData[y][x] + ]); + } + } + } + + return changes; + } else { + if(this.props.toast) + this.props.toast.show({ + content: "No image to generate pixel art from", + severity: 'error', + closable: false + }); + return []; + } + } + + //calculate flat bytearray for multidimensional animation array + private getAnimationArray(changes: number[][][]): number[] { + let byteArray: number[] = []; + + changes.forEach(frame => { + //frame separator and for future metadata usage -> address [255,255] does not exist + byteArray.push(0xFF, 0xFF, 0, 0); + + //pixelchanges + frame.forEach(pixel => { + byteArray.push(pixel[0], pixel[1], pixel[2] & 255, (pixel[2] >> 8) & 255); + }); + }); + + return byteArray; + } + + //set canvas pixels by flat bytearray + private setPixels(pixels: number[], canvas: CanvasRenderingContext2D = this.getCanvas()) { + var id = canvas.createImageData(1,1); for(var y = 0; y < 64; y++) { for(var x = 0; x < 64; x++) { - var pixel = rgb888(pixels[y * 64 + x]); + var location = (y * 64 + x) * 2; + var pixel = rgb888((pixels[location + 1] << 8) + pixels[location]); id.data[0] = pixel.r; id.data[1] = pixel.g; id.data[2] = pixel.b; id.data[3] = 255; - ctx.putImageData(id, x, y); + canvas.putImageData(id, x, y); } } } - private clearImage() { - let ctx = this.getCanvas(); - - var id = ctx.createImageData(1,1); + //remove current preview + private clearCanvas() { + let canvas = this.getCanvas(); + var id = canvas.createImageData(1,1); id.data.fill(0); for(var y = 0; y < 64; y++) { for(var x = 0; x < 64; x++) { - ctx.putImageData(id, x, y); + canvas.putImageData(id, x, y); } } + + if(this.state.frameInterval) + clearInterval(this.state.frameInterval); + this.setState({ + canvasImages: [], + frameInterval: null, + name: "", + rendering: false, + viewHistory: false + }); } - private getPixelArray() { - - let ctx = this.getCanvas(); + //get flat bytearray from canvas + private getPixelArray(canvas: CanvasRenderingContext2D = this.getCanvas()): number[] { let pixels = []; - for(var y = 0; y < 64; y++) { for(var x = 0; x < 64; x++) { - var pixel = ctx.getImageData(x, y, 1, 1); + var pixel = canvas.getImageData(x, y, 1, 1); let pixelInt = pixel.data[3] === 0 ? 0 : rgb565(pixel.data[0], pixel.data[1], pixel.data[2]); - pixels.push(pixelInt & 255); - pixels.push((pixelInt >> 8) & 255); + pixels.push(pixelInt & 255, (pixelInt >> 8) & 255); } } - return pixels; } - private get2dPixelArray() { - - let ctx = this.getCanvas(); + //get 2d pixel array from canvas (pixel[y][x]) + private get2dPixelArray(canvas: CanvasRenderingContext2D = this.getCanvas()): number[][] { let pixels: number[][] = []; - for(var y = 0; y < 64; y++) { pixels[y] = []; for(var x = 0; x < 64; x++) { - var pixel = ctx.getImageData(x, y, 1, 1); + var pixel = canvas.getImageData(x, y, 1, 1); pixels[y][x] = pixel.data[3] === 0 ? 0 : rgb565(pixel.data[0], pixel.data[1], pixel.data[2]) } } - return pixels; } + + //save preview image/animation to cache + private cacheImage(name: string, image: number[], animation: number[][][] = [], preview: string = "") { + let index: number = 0; + let cacheItem: string | null = null; + do { + index++; + cacheItem = localStorage.getItem("image-" + index); + if(index > 40) { + index = 1; + break; + } + } while(cacheItem != null); + + let cachedImage: ICachedImage = { + id: index, + name: name, + preview: preview, + created: moment().unix(), + animation: animation, + image: image, + }; + localStorage.setItem("image-" + index, JSON.stringify(cachedImage)); + this.setState({ + cachedItems: [...this.state.cachedItems, cachedImage] + }); + } + + //load images from cache into state + private loadCache() { + let images: ICachedImage[] = []; + for(let index: number = 1; index <= 40; index++) { + let cacheItem = localStorage.getItem("image-" + index); + if(cacheItem) { + let cachedImage = JSON.parse(cacheItem) as ICachedImage; + if(cachedImage.id && cachedImage.name && cachedImage.image) { + images.push(cachedImage); + } + } + } + + this.setState({ + cachedItems: images.sort((i1, i2) => i2.created - i1.created) + }); + } + + //load image from cache into preview + private loadFromCache(name: string, image: number[], animation: number[][][]) { + let ctx = this.newCanvas(); + let frames: ImageData[] = []; + + //load image on canvas + this.setPixels(image, ctx); + frames.push(ctx.getImageData(0, 0, 64, 64)); + + //load frames + var id = ctx.createImageData(1,1); + animation.forEach((frame, index) => { + //push before foreach to dont push loopback frame (last frame) + if(index !== 0) + frames.push(ctx.getImageData(0, 0, 64, 64)); + + frame.forEach(change => { + var pixel = rgb888(change[2]); + id.data[0] = pixel.r; + id.data[1] = pixel.g; + id.data[2] = pixel.b; + id.data[3] = 255; + ctx.putImageData(id, change[0], change[1]); + }); + }); + + this.currentFrame = 0; + this.setState({ + name: name, + canvasImages: frames, + viewHistory: false, + }, () => setTimeout(() => this.drawFromImages(), 50)); + } } \ No newline at end of file diff --git a/pixelart-interface/src/components/pages/Home.tsx b/pixelart-interface/src/components/pages/Home.tsx index f73f8b5..3ddd3a0 100644 --- a/pixelart-interface/src/components/pages/Home.tsx +++ b/pixelart-interface/src/components/pages/Home.tsx @@ -1,8 +1,16 @@ import DataService from '../../services/DataService'; import React from 'react'; import { Toast } from 'primereact/toast'; +import { Link, Location } from 'react-router-dom'; +import { Fieldset } from 'primereact/fieldset'; +import { BiImages, BiTime, BiUser, BiWifi } from 'react-icons/bi'; +import { RxUpdate } from 'react-icons/rx'; +import { IoShareSocialSharp, IoWarningOutline } from 'react-icons/io5'; +import { scrollIntoView } from '../../services/Helper'; +import { Chip } from 'primereact/chip'; export interface IHomeComponentProps { + location: Location; dataService: DataService; toast: Toast | null; } @@ -18,15 +26,195 @@ export default class Home extends React.Component, prevState: Readonly, snapshot?: any): void { + if(prevProps.location.hash !== this.props.location.hash) { + scrollIntoView(this.props.location.hash.replace("#", "")); + } } public render() { + console.log(this.props.location.hash); return(
- TBD How to guide +

DerEffi's Pixelart

+ +
overview of pixelart device
+ +
+ This Webinterface provides the basic usage of the pixelart display as well as an Image Generator to convert your pictures into a for the display usable format. You can also remote control your device, if you choose to connect your display to a network or via Hotspot directly to your device. WiFi functionality is completely optional, although you can't use the 'Social' mode when not connected to the internet for obvious reasons. +
+
+ If you want to build a pixelart display yourself, take a look on GitHub. It is open source with requirements and build instructions waiting for you. +
+ +
General
} id="general"> + +
input on the back of the device
+
+ + You can control the device with the following buttons and knobs. + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NextSwitch to the next element in the current mode or menu
ModeSwitch to the next mode or change the selected menu item
MenuOpen/Close the menu
DiashowAdjust the speed for switching through elements in and between modes
(Press) Start/Stop the Diashow
AnimationAdjust the speed for switching through the frames of an image
(Press) Start/Stop the Animation
BrightnessAdjust the brightness of the LED panel
+ +
+ + These functions can also be controlled over the network once your display is connected, although it may take a second to apply the new preferences over the air. + + + +
Pictures
} id="pictures"> + +
example of a generated pixelart frame
+
+ + The panel needs a special file format for the pictures you want to display. You can generate these files from images with the Picture Generator. The processed files can be downloaded in a zip and placed in the SD cards folder or directly uploaded if your display is connected to the network. If the folder doesn't exist, feel free to create one with the name . + +
+
+ + Notes on using the image generator: + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Aspect ratioUse square images - Different aspect ratios will result in streched pixelarts
SizeThe display has a size of 64x64 pixels. Although the generator resizes your image without any edge-interpolation you might loose some details, so for the best results you may want to manually edit your images to exactly that resolution
Color accuracyBecause of the limited color-depth of the Display you might want to try experimenting with the colors in your image. I personally like to turn up contrast or turn down the highlights a bit, because you can't distinguish pretty similar pixels and would loose a lot of details otherwise. FYI: The panel uses RGB-565 and displays in 6bit color depth, but due to the color correction of the individuals LEDs the representation on the display itself might not be always accurate.
AnimationsDue to the limited memory big animations with lots of pixel changes might cause problems. If your device does not start or load the images properly you might want to try removing the animation from the SD card. The memory consumption depends mostly on the number of pixels changed between frames (please accord for the additional loopback frame) and based on my tests you should get away with 6 complete redraws of the image.
FrametimeSince the Pixelart display has it's own knob to control the speed of the animations, individual frametimes of your gif will be ignored. If you wish to display a single frame for longer, you can insert empty or duplicate frames.
PreviewDepending on your browser, the preview image(s) can juggle a bit during animations. This issue affects only the live preview of your uploaded images and will not be visible on your device after generation.
+ +
+ + After uploading your image to the device, you can change the order on the Pictures tab. You can also change the order by renaming the 3 digit prefix of the folders on your sd card. The numbers dont have to be consecutive. They will be ordered on device startup from smallest to highest. + + + +
Time
} id="time"> + +
different clock modes
+
+ + The Clock mode has different layouts you can switch through with the button. For most layouts you can adjust preferences in the menu. If connected to the network you can also change the settings with the webinterface or update your time automatically over wifi. Depending on where you live you might want to adjust your timezone by activating the advanced mode in the webinterface. + +
+
+ + If your device doesn't save your time after turning it off, you might want to check your battery (CR2032) by opening the screws on the back of your panel. + + + +
Wifi
} id="wifi"> + To connect your dispay to a network you first need to enable the option in the onboard menu so that you can directly connect to the display itself with a browser of your choice. If you are unsure about the connection details, you can check the section for the current settings. +
+
+ After connecting to the display's network, you should be redirected to webinterface, where you can enter your network information. If not you can manually browse to 192.168.4.1. Once you are connected to a network your device's IP-Adress will change. Either check your network or the display's onboard menu under to find it. +
+ To adjust your preferences you can use the webinterface on the device itself or navigate to pixelart.dereffi.de and enter the IP-Adress in the sidemenu on the left. +
+
+ Make sure to deactivate the Host mode when you are finished setting up your device as it might occupy unnecessary resources you might rather want to use for a big animation... + + + +
Socials
} id="socials"> + +
example of social mode
+
+ + Since you need to connect the display to the network anyway, you can only adjust this mode on the webinterface. To set it up check the System tab on the left. +
+
+ If connected to the network and after you added at least one social channel, this will show up, when you cycle through the modes on your display. You can also use the diashow as you are used to from your pictures and under Display you can check the option to switch between pictures and socials automatically. +
+
+ Depending on the kind of data and source some stats might be cached for a short amount of time either by me, the display or the social media platform. As long as you stay connected to the network the stats get updated automatically, but you might see let's say your viewers number go up from a twitch raid one or two minutes later. +
+ + +
Updates
} id="updates"> + In the Downloads tab you can download the newest version of the firmware and webinterface and place the files on the SD card. The update will be performed on the next start and should only take a few seconds. If you update the firmware, please don't disconnect the power from your device as it might brick the display. +
+
+ Notice: If you are or know a software developer you can also compile the firmware/webinterface from my GitHub with your personal touch yourself and copy the files onto the SD card. +
+ + +
Problems
} id="problems"> + If you have any problems with your device, sometimes a simple restart can fix it... +
+ The power supply needs a few seconds to discharge, so don't flip the switch back on immediatly and either wait for the display to turn off or if the display isn't showing anything 2 or 3 seconds. +
+
+ Ocassonaly the display needs some time to connect to your Wifi network. If you were already connected to a network or know that your connection details are correct, please have a little patience. After a few minutes your device should get a connection. If not please check if your sign-in information is correct. +
+
+ If your problem persist you can reset the device to "Factoy Settings" with the webinterface under System or press and hold the button on your display for 10s. +
+
+ Still not fixed? - Sent me the details maybe you found a mistake I made (info@dereffi.de) or open a ticket on GitHub +
diff --git a/pixelart-interface/src/components/pages/Settings.tsx b/pixelart-interface/src/components/pages/Settings.tsx index aa8e092..de59979 100644 --- a/pixelart-interface/src/components/pages/Settings.tsx +++ b/pixelart-interface/src/components/pages/Settings.tsx @@ -37,58 +37,63 @@ export default class Settings extends React.Component - -
+ <> {this.props.dataService.getStatus() === Status.connected && - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> } /> } {this.props.dataService.getStatus() === Status.unauthorized && -
- - - - - - -
- Please press the 'Menu' button on your device -
- to establish a connection +
+
+ + + + + + +

+ Please press the 'Menu' button on your device +
+ to establish a connection +

+
} {this.props.dataService.getStatus() === Status.disconnected && -
- - - -
- Could not establish connection with your device -
- on the address '{this.props.dataService.getDeviceAddress()}' +
+
+ + + + +

+ Could not establish a connection with your device +
+ on the address '{this.props.dataService.getDeviceAddress()}' +

+
} {this.props.dataService.getStatus() === Status.pending && -
- +
+
+ +
} -
- -
+ ); } } \ No newline at end of file diff --git a/pixelart-interface/src/components/pages/Settings/DisplaySettings.tsx b/pixelart-interface/src/components/pages/Settings/DisplaySettings.tsx index bc38e5c..dfde2ff 100644 --- a/pixelart-interface/src/components/pages/Settings/DisplaySettings.tsx +++ b/pixelart-interface/src/components/pages/Settings/DisplaySettings.tsx @@ -6,15 +6,20 @@ import { Toast } from 'primereact/toast'; import { APIError } from '../../../models/Errors'; import { Slider } from 'primereact/slider'; import { Button } from 'primereact/button'; +import { Checkbox } from 'primereact/checkbox'; +import { Tooltip } from 'primereact/tooltip'; +import { InputNumber } from 'primereact/inputnumber'; export interface IDisplaySettingsComponentProps { dataService: DataService; toast: Toast | null; + advanced: boolean; } interface IDisplaySettingsComponentState { - brightness: number; - brightnessChanged: boolean; + brightness?: number; + animationTime?: number; + diashowTime?: number; } export default class DisplaySettings extends React.Component { @@ -23,16 +28,6 @@ export default class DisplaySettings extends React.Component, prevState: Readonly, snapshot?: any): void { - if(!this.state.brightnessChanged && this.props.dataService.data.display && this.state.brightness !== this.props.dataService.data.display?.brightness) { - this.setState({ - brightness: this.props.dataService.data.display.brightness - }); } } @@ -44,19 +39,19 @@ export default class DisplaySettings extends React.Component
Display Mode
-
this.setDisplayMode(0) } className={ this.props.dataService.data.display?.displayMode === 0 ? "selected" : ""}> +
this.setDisplayData({displayMode: 0}) } className={ this.props.dataService.data.display?.displayMode === 0 ? "selected" : ""}>
Pictures
-
this.setDisplayMode(1) } className={ this.props.dataService.data.display?.displayMode === 1 ? "selected" : ""}> +
this.setDisplayData({displayMode: 1}) } className={ this.props.dataService.data.display?.displayMode === 1 ? "selected" : ""}>
Clock
-
this.setDisplayMode(2) } className={ this.props.dataService.data.display?.displayMode === 2 ? "selected" : ""}> +
this.setDisplayData({displayMode: 2}) } className={ this.props.dataService.data.display?.displayMode === 2 ? "selected" : ""}>
@@ -68,19 +63,94 @@ export default class DisplaySettings extends React.Component
Brightness
- this.setState({brightnessChanged: true, brightness: e.value as number})} /> -
{this.state.brightness}
-
+
+ +
+
Diashow
+
+ {!this.props.advanced && + <> + Imagetime + this.setState({diashowTime: 60000 - (e.value as number)})} /> +
{(this.state.diashowTime || this.props.dataService.data.display?.diashowTime || 1000) / 1000}s
+ + } + {this.props.advanced && + <> + this.setState({diashowTime: e.value as number})} />ms + + } +
+ +
+
Animation
+
+ {!this.props.advanced && + <> + Frametime + this.setState({animationTime: 500 - (e.value as number)})} /> +
{(this.state.animationTime || this.props.dataService.data.display?.animationTime || 20)}ms
+ + } + {this.props.advanced && + <> + this.setState({animationTime: e.value as number})} />ms + + } +
+
+ + + + + + + + +
Diashow between modes this.setDisplayData({diashowModes: e.target.checked})} />
); } - public setBrightness(val: number) { + private setBrightness(val: number) { this.props.dataService.requestDevice("POST", "/api/display", {brightness: val}) - .then(() => this.props.dataService.refresh().then(() => this.setState({brightnessChanged: false}))) + .then(() => this.props.dataService.refreshDisplay().then(() => this.setState({brightness: undefined}))) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private setDiashowTime(val: number) { + this.props.dataService.requestDevice("POST", "/api/display", {diashowTime: val, diashow: true}) + .then(() => this.props.dataService.refreshDisplay().then(() => this.setState({diashowTime: undefined}))) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private setAnimationTime(val: number) { + this.props.dataService.requestDevice("POST", "/api/display", {animationTime: val, animation: true}) + .then(() => this.props.dataService.refreshDisplay().then(() => this.setState({animationTime: undefined}))) .catch((e: APIError) => { if(this.props.toast) this.props.toast.show({ @@ -91,9 +161,9 @@ export default class DisplaySettings extends React.Component this.props.dataService.refresh()) + private setDisplayData(data: any) { + this.props.dataService.requestDevice("POST", "/api/display", data) + .then(() => this.props.dataService.refreshDisplay()) .catch((e: APIError) => { if(this.props.toast) this.props.toast.show({ diff --git a/pixelart-interface/src/components/pages/Settings/PictureSettings.tsx b/pixelart-interface/src/components/pages/Settings/PictureSettings.tsx index 192e413..d62dffb 100644 --- a/pixelart-interface/src/components/pages/Settings/PictureSettings.tsx +++ b/pixelart-interface/src/components/pages/Settings/PictureSettings.tsx @@ -1,14 +1,30 @@ import DataService from '../../../services/DataService'; import React from 'react'; import { Toast } from 'primereact/toast'; +import { IImageData, IImageOperation } from '../../../models/Settings'; +import { Dialog } from 'primereact/dialog'; +import { Button } from 'primereact/button'; +import { OrderList } from 'primereact/orderlist'; +import { Tooltip } from 'primereact/tooltip'; +import { CgMenu } from 'react-icons/cg'; +import { FiEdit2 } from 'react-icons/fi'; +import { BiImage, BiMoviePlay, BiUndo } from 'react-icons/bi'; +import { APIError } from '../../../models/Errors'; +import { InputText } from 'primereact/inputtext'; +import { padLeft } from '../../../services/Helper'; +import { v4 as uuid } from 'uuid'; +import { HiArrowNarrowRight } from 'react-icons/hi'; +import { MdDelete } from 'react-icons/md'; export interface IPictureSettingsComponentProps { dataService: DataService; toast: Toast | null; + advanced: boolean; } interface IPictureSettingsComponentState { - + images?: IImageData[]; + editItem?: IImageData; } export default class PictureSettings extends React.Component { @@ -25,12 +41,179 @@ export default class PictureSettings extends React.Component
+ + this.setState({editItem: undefined})} + > + <> + {this.state.editItem && +
+
+
Image Name
+ { this.updateEditItem(e.target.value) }} /> +
- TBD Picture settings +
+ +
+
+ } + +
+ +
+
Display Image
+
+
+
+ + {!this.props.advanced && + <> + +
+ + + {item.delete ? this.deleteImage(item)} /> : this.deleteImage(item)} />} + this.setState({editItem: item})} /> + +
+ {item.animated ? : } +
{item.rename || item.folder}
+
+
+ } + onChange={(e) => this.updateImages(e.value)} + /> + + } + + {this.props.advanced && + + + {(this.state.images || this.props.dataService.data.images?.images || []).map((image) => + + + + + + + + )} + +
{image.delete ? this.deleteImage(image)} /> : this.deleteImage(image)} />}{image.animated ? : }{image.folder} image.delete ? () => {} : this.advancedEditItem(image, e.target.value)} />
+ } + +
+
+
+
- +
); } + + private updateEditItem(name: string) { + if(this.state.editItem !== undefined) { + let image = this.state.editItem; + image.rename = padLeft(image.prefix, 3) + " - " + name; + this.setState({ + editItem: image + }); + } + } + + private deleteImage(image: IImageData) { + image.delete = !image.delete; + this.updateImages(this.state.images || this.props.dataService.data.images?.images || []); + } + + private advancedEditItem(image: IImageData, name: string) { + let images = this.state.images || this.props.dataService.data.images?.images || []; + images.forEach(i => { + if(i.folder === image.folder) + i.rename = name; + }); + this.setState({ + images: images + }); + } + + private updateImages(images: IImageData[]) { + let counter = 0; + + this.setState({ + images: images.map(i => { + i.prefix = i.delete ? 0 : ++counter; + i.rename = `${padLeft(i.prefix, 3)} - ${(i.rename || i.folder).substring(6)}` + return i; + }) + }); + } + + private async setImageData(data: any) { + await this.props.dataService.requestDevice("POST", "/api/images", data) + .then(() => this.props.dataService.refreshImages()) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private async sendFileOperations(images: IImageData[]) { + let sourceOperations: IImageOperation[] = []; + let deleteOperations: IImageOperation[] = []; + let targetOperations: IImageOperation[] = []; + + images.forEach(image => { + if(image.delete) { + deleteOperations.push({src: image.folder}); + } else if(image.rename) { + let id: string = uuid(); + sourceOperations.push({src: image.folder, dst: id}); + deleteOperations.push({src: image.rename}); + targetOperations.push({src: id, dst: image.rename}); + } + }); + + await this.props.dataService.requestDevice("POST", "/api/images", {imageOperations: [...sourceOperations, ...deleteOperations, ...targetOperations]}) + .then(() => setTimeout(() => { + this.props.dataService.refreshImages() + .then(() => { + this.setState({ + editItem: undefined, + images: undefined + }); + }); + }, 1000)) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } } \ No newline at end of file diff --git a/pixelart-interface/src/components/pages/Settings/SocialsSettings.tsx b/pixelart-interface/src/components/pages/Settings/SocialsSettings.tsx index 1495422..057bea3 100644 --- a/pixelart-interface/src/components/pages/Settings/SocialsSettings.tsx +++ b/pixelart-interface/src/components/pages/Settings/SocialsSettings.tsx @@ -1,14 +1,40 @@ import DataService from '../../../services/DataService'; import React from 'react'; import { Toast } from 'primereact/toast'; +import { Button } from 'primereact/button'; +import { APIError } from '../../../models/Errors'; +import { InputText } from 'primereact/inputtext'; +import { InputTextarea } from 'primereact/inputtextarea'; +import axios from 'axios'; +import { SocialItem } from '../../../models/Socials'; +import { Dialog } from 'primereact/dialog'; +import { OrderList } from 'primereact/orderlist'; +import { IoShareSocial } from 'react-icons/io5'; +import { BsInstagram, BsTwitch, BsYoutube } from 'react-icons/bs'; +import { Link } from 'react-router-dom'; +import {v4 as uuid} from 'uuid'; +import { CgMenu } from 'react-icons/cg'; +import { Dropdown } from 'primereact/dropdown'; +import { FiEdit2 } from 'react-icons/fi'; +import { Tooltip } from 'primereact/tooltip'; +import { BiUndo } from 'react-icons/bi'; +import { MdDelete } from 'react-icons/md'; export interface ISocialsSettingsComponentProps { dataService: DataService; toast: Toast | null; + advanced: boolean; } interface ISocialsSettingsComponentState { - + request?: string; + apiKey?: string; + server?: string; + testResponse: SocialItem[]; + loadingTest: boolean; + socialList: SocialItem[]; + lastDataServiceRequest: string; + editItem?: string; } export default class SocialsSettings extends React.Component { @@ -17,7 +43,19 @@ export default class SocialsSettings extends React.Component, prevState: Readonly, snapshot?: any): void { + if(this.state.request === undefined && this.state.lastDataServiceRequest !== (this.props.dataService.data.socials?.request || "")) + this.setState({ + lastDataServiceRequest: (this.props.dataService.data.socials?.request || ""), + socialList: this.decodeSocialRequest((this.props.dataService.data.socials?.request || "")) + }); } public render() { @@ -25,12 +63,354 @@ export default class SocialsSettings extends React.Component
+ + this.setState({editItem: undefined})} + > + <> + {this.renderEditDialog(this.state.editItem as string)} + + + + 0} + className="socials-test content" + headerClassName="socials-test-header" + onHide={() => this.setState({testResponse: []})} + > +
+ {this.state.testResponse.map(social => { + + let icon: JSX.Element = ; + let url: string = ""; + let color: string = "#FFFFFF"; + switch(social.t) { + case "t": + icon = ; + url = "https://www.twitch.tv/" + social.c; + color = "#6441a5"; + break; + case "y": + icon = ; + url = "https://www.youtube.com/" + social.c; + color = "#FF0000"; + break; + case "i": + icon = ; + url = "https://www.instagram.com/" + social.c; + color = "#E1306C"; + break; + } + + return( + +
+
{icon}
+
{social.d || social.c}
+
{social.f}
+
{social.v}
+
+ + ); + })} +
+
+ +
+
Display Channel
+
+
+
+ + {!this.props.advanced && + <> + +
+ + + {item.delete ? this.deleteSocialItem(item)} /> : this.deleteSocialItem(item)} />} + this.setState({editItem: item.id})} /> + +
+ {item.t === "t" && + + } + {item.t === "y" && + + } + {item.t === "i" && + + } + +
{item.d || item.c}
+
{item.d ? `(${item.c})` : ""}
+
+
+ } + onChange={(e) => this.updateRequest(e.value)} + /> +
+
+ + } - TBD Socials settings + {this.props.advanced && + <> +
+
Request
+
+ this.setState({request: e.target.value, socialList: this.decodeSocialRequest()})} /> +
+
+ +
+
Server
+
+ this.setState({server: e.target.value})} /> +
+
+ +
+
APIKey
+
+ this.setState({apiKey: e.target.value})} /> +
+
+ + } + +
+
+
+ + {!this.props.advanced && + <> +
+
+
+ + } +
- +
); } + + private renderEditDialog(id: string): JSX.Element { + let items: SocialItem[] = this.state.socialList.filter(i => i.id === id); + if(items.length !== 1) + return <>; + + let item = items[0]; + return( +
+
+
Platform
+ { let newItem = item; newItem.t = e.value; this.updateSocialItem(newItem); }} + options={[ + {value: "t", label: "Twitch"}, + {value: "y", label: "Youtube"}, + {value: "i", label: "Instagram"} + ]} + /> +
+
+
Display Name
+ { item.d = e.target.value; this.updateSocialItem(item); }} /> +
+
+
Channel Name
+ { item.c = e.target.value; this.updateSocialItem(item); }} /> +
+ +
+ +
+
+ ); + } + + private decodeSocialRequest(request: string = (this.state ? this.state.request : undefined) || this.props.dataService.data.socials?.request || "[]"): SocialItem[] { + try { + return (JSON.parse(request) as SocialItem[]).map((i) => { i.id = uuid(); return i;}); + } catch { + return []; + } + } + + private updateRequest(items: SocialItem[]) { + this.setState({ + socialList: items, + request: JSON.stringify([...items].map(x => ({...x})).map(x => {x.id = undefined; return x;})) + }); + } + + private updateSocialItem(newItem: SocialItem) { + let updated = this.state.socialList.map(item => { + if(item.id === newItem.id) + item = newItem; + return item; + }); + this.setState({ + socialList: updated, + request: JSON.stringify([...updated].map(x => ({...x})).filter(x => !x.delete).map(x => {x.id = undefined; x.delete = undefined; return x;})) + }); + } + + private deleteSocialItem(item: SocialItem) { + item.delete = !item.delete; + this.updateSocialItem(item); + } + + private addSocialItem() { + let id = uuid(); + this.setState({ + socialList: [...this.state.socialList, { + t: "t", + c: "", + d: "", + f: "", + v: "", + id: id + }], + editItem: id + }); + } + + private setSocialData(data: any) { + this.props.dataService.requestDevice("POST", "/api/socials", data) + .then(() => this.props.dataService.refreshSocials()) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private setSocialServer(server: string) { + this.props.dataService.requestDevice("POST", "/api/socials", {server: server}) + .then(() => this.props.dataService.refreshSocials().then(() => this.setState({server: undefined}))) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private setSocialApiKey(apiKey: string) { + this.props.dataService.requestDevice("POST", "/api/socials", {apiKey: apiKey}) + .then(() => this.props.dataService.refreshSocials().then(() => this.setState({apiKey: undefined}))) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private setSocialRequest(request: string) { + //check for json format and strip unnecessary spaces + try { + let jsonRequest = JSON.parse(request); + request = JSON.stringify(jsonRequest); + } catch { + if(this.props.toast) + this.props.toast.show({ + content: "Your request data is not in the exspected format", + severity: 'error', + closable: false + }); + return; + } + + this.props.dataService.requestDevice("POST", "/api/socials", {request: request}) + .then(() => this.props.dataService.refreshSocials().then(() => this.setState({request: undefined}))) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private testSocials(server: string, apiKey: string, request: string) { + + this.setState({loadingTest: true}); + + let jsonRequest; + try { + jsonRequest = JSON.parse(request); + } catch { + if(this.props.toast) + this.props.toast.show({ + content: "Your request data is not in the exspected format", + severity: 'error', + closable: false + }); + return; + } + + axios.post(server + "/socials", jsonRequest, { + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": apiKey + } + }) + .then((resp) => { + if(resp.status !== 200) + throw new Error("" + resp.status); + + this.setState({testResponse: resp.data as SocialItem[], loadingTest: false}); + }) + .catch(() => { + if(this.props.toast) + this.props.toast.show({ + content: "The server reporeted an error with your request", + severity: 'error', + closable: false + }); + }); + } } \ No newline at end of file diff --git a/pixelart-interface/src/components/pages/Settings/SystemSettings.tsx b/pixelart-interface/src/components/pages/Settings/SystemSettings.tsx index c165936..9fdc418 100644 --- a/pixelart-interface/src/components/pages/Settings/SystemSettings.tsx +++ b/pixelart-interface/src/components/pages/Settings/SystemSettings.tsx @@ -1,36 +1,418 @@ import DataService from '../../../services/DataService'; import React from 'react'; import { Toast } from 'primereact/toast'; +import { Chart } from 'primereact/chart'; +import moment from 'moment'; +import 'moment-duration-format'; +import { Button } from 'primereact/button'; +import axios from 'axios'; +import { VersionDetails } from '../../../models/Version'; +import * as semver from 'semver'; +import { asyncTimeout } from '../../../services/Helper'; +import { APIError } from '../../../models/Errors'; +import { ConfirmDialog } from 'primereact/confirmdialog'; +import { v4 as uuid } from 'uuid'; +import { Messages } from 'primereact/messages'; +import { BiInfoCircle } from 'react-icons/bi'; export interface ISystemSettingsComponentProps { dataService: DataService; toast: Toast | null; + advanced: boolean; } interface ISystemSettingsComponentState { - + downloading: boolean; + showResetConfirmDialog: boolean; } +const maxDataPoints: number = 50; + export default class SystemSettings extends React.Component { + private messages: Messages | null = null; + constructor(props: ISystemSettingsComponentProps) { super(props); this.state = { - } + downloading: false, + showResetConfirmDialog: false + }; } public render() { + let statisticsData = this.props.dataService.statistics.slice(this.props.dataService.statistics.length >= maxDataPoints ? this.props.dataService.statistics.length - maxDataPoints : 0, this.props.dataService.statistics.length); + return(
+ + this.setState({showResetConfirmDialog: false})} + rejectLabel="Abort" + accept={() => this.reset()} + acceptLabel='Reset' + acceptIcon="pi pi-trash" + message={ +
+

Resetting the device to "Factory" Settings +
will remove all your adjustments

+

Do you want to proceed?

+
+ } + /> + + this.messages = el} style={{textAlign: 'left'}} /> + + + + + + + + + + + + + + + + + + + + + + + + + + {this.props.advanced && + + + + } + + + + + + + +
CurrentNewest
Firmware{this.props.dataService.data.display?.version || "-"}{this.props.dataService.newestFirmware?.version || "-"} +
Webinterface{this.props.dataService.deviceWebinterfaceVersion?.version || "-"}{this.props.dataService.newestWebinterface?.version || "-"} +
+
+
+
+
- TBD System settings + {this.props.advanced && + <> +
+
Statistics
+
+ + s.internal) + }, + { + label: "External Memory", + borderColor: getComputedStyle(document.documentElement).getPropertyValue('--green-500'), + borderWidth: 2, + pointHitRadius: 15, + data: statisticsData.map(s => s.external) + } + ], + labels: statisticsData.map(s => s.time.format("HH:mm:ss")), + } as Chart.ChartData} + options={{ + scales: { + y: { + type: "linear", + position: "left" + }, + }, + animation: false + } as ChartOptions} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Display Refresh Rate{this.props.dataService.data.display?.refreshRate}Hz
Image loaded from SD Card{this.props.dataService.data.images?.imageLoaded ? "Yes" : "No"}
Soial Channels loaded{this.props.dataService.data.socials?.channelNumber}
Online time{this.props.dataService.data.time ? moment.duration(this.props.dataService.data.time.online, "milliseconds").format("DD[d] HH[h] mm[m] ss[s]") : "-"}
External RTC connected{this.props.dataService.data.time?.externalRTCConnected ? "Yes" : "No"}
Free Memory{this.props.dataService.data.display?.freeMemory.toLocaleString()} byte
Free Memory (SPI){this.props.dataService.data.display?.freeSPIMemory.toLocaleString()} byte
+ + + }
); } + + public async performUpdate(version?: VersionDetails) { + this.setState({ + downloading: true + }); + + try { + + if(!version) + throw new Error("No version information available"); + + //download files from update server + let files: {content: ArrayBuffer, path: string}[] = await Promise.all( + version.files.map(async path => + new Promise((resolve, reject) => { + axios({ + url: `${process.env.REACT_APP_UPDATE_SERVER}/${version.type}/${path}`, + method: "GET", + responseType: "arraybuffer" + }).then(resp => { + if(resp.status !== 200) + reject(); + + resolve({ + path: path, + content: resp.data + }); + }).catch((e) => { + reject(e); + }) + }) + ) + ); + + //upload update to device + if(version.type !== "webinterface" && version.type !== "firmware") + throw new Error("Unexpected update type"); + + let upload: FormData = new FormData(); + files.forEach(file => { + upload.append(file.path, new Blob([file.content], {type: "octet/stream"}), uuid()); + }) + await this.props.dataService.uploadFiles(version.type, upload).then(async () => { + if(this.props.toast) + this.props.toast.show({ + content: "Update uploaded to your device", + severity: 'success', + closable: false + }); + + if(this.messages && version.type === "firmware") + this.messages.show({ + closable: true, + sticky: true, + severity: "info", + content: <> + +
+

+ The update has been uploaded to your device and will apply on the next start. +
+
+

this.restart()}>Restart now
+

+
+ , + }); + }).catch((e) => { + if(this.props.toast) + this.props.toast.show({ + content: "There was a problem uploding the update to your device", + severity: 'error', + closable: false + }); + }); + + } catch(e) { + if(this.props.toast) + this.props.toast.show({ + content: "There was an issue updating your device", + severity: 'error', + closable: false + }); + } + + this.setState({ + downloading: false + }); + } + + private restart() { + this.props.dataService.requestDevice("POST", "/api/restart") + .then(async () => { + await asyncTimeout(10000); + await this.props.dataService.refresh().catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + }) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private refresh() { + this.props.dataService.requestDevice("POST", "/api/refresh") + .then(async () => { + await asyncTimeout(100); + await this.props.dataService.refresh().catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + }) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private reset() { + this.props.dataService.requestDevice("POST", "/api/reset") + .then(async () => { + await asyncTimeout(10000); + await this.props.dataService.refresh().catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + }) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } + + private resetAPIKey() { + this.props.dataService.requestDevice("DELETE", "/api/apiKey") + .then(async () => { + await asyncTimeout(100); + await this.props.dataService.refresh().catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + }) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } } \ No newline at end of file diff --git a/pixelart-interface/src/components/pages/Settings/TimeSettings.tsx b/pixelart-interface/src/components/pages/Settings/TimeSettings.tsx index 68af3cf..6dce970 100644 --- a/pixelart-interface/src/components/pages/Settings/TimeSettings.tsx +++ b/pixelart-interface/src/components/pages/Settings/TimeSettings.tsx @@ -12,20 +12,21 @@ import { Dropdown } from 'primereact/dropdown'; import { timezoneOptions } from '../../shared/Timezones'; import { Checkbox } from 'primereact/checkbox'; import { Calendar } from 'primereact/calendar'; +import { InputNumber } from 'primereact/inputnumber'; +import { InputSwitch } from 'primereact/inputswitch'; import moment from 'moment'; export interface ITimeSettingsComponentProps { dataService: DataService; toast: Toast | null; + advanced: boolean; } interface ITimeSettingsComponentState { - ntpServer: string; - ntpServerChanged: boolean; + ntpServer?: string; timezone: string; + timezoneChanged: boolean; deviceTime?: Date; - deviceTimeChanged: boolean; - deviceTimePulled: boolean; } export default class TimeSettings extends React.Component { @@ -34,28 +35,15 @@ export default class TimeSettings extends React.Component, prevState: Readonly, snapshot?: any): void { - if(!this.state.ntpServerChanged && this.props.dataService.data.time && this.state.ntpServer !== this.props.dataService.data.time.ntpServer) { - this.setState({ - ntpServer: this.props.dataService.data.time.ntpServer - }); - } - - if(this.state.timezone && this.props.dataService.data.time && this.props.dataService.data.time.timezone !== this.state.timezone.substring(this.state.timezone.indexOf("%") + 1, this.state.timezone.length)) { + if(!this.state.timezoneChanged && this.state.timezone && this.props.dataService.data.time && this.props.dataService.data.time.timezone !== this.state.timezone.substring(this.state.timezone.indexOf("%") + 1, this.state.timezone.length)) { this.setState({timezone: ""}); } - - if(!this.state.deviceTimeChanged && !this.state.deviceTimePulled && this.props.dataService.data.time) { - this.setState({deviceTimePulled: true, deviceTime: moment.unix(this.props.dataService.data.time.time).toDate()}); - } } public render() { @@ -96,23 +84,38 @@ export default class TimeSettings extends React.Component
Device Time
- this.setState({deviceTimeChanged: true, deviceTime: e.target.value ? e.target.value as Date : undefined})} showTime hourFormat={this.props.dataService.data.time && !this.props.dataService.data.time.format24 ? "12" : "24"} dateFormat="dd.mm.yy" /> -
-
-
Time Server
-
- this.setState({ntpServerChanged: true, ntpServer: e.target.value})} /> -
-
+ }
Timezone
- this.setTimezone(e.target.value)} optionLabel="name" optionGroupLabel="label" optionGroupChildren="items"/> + {!this.props.advanced && + this.setTimezone(e.target.value)} optionLabel="name" optionGroupLabel="label" optionGroupChildren="items"/> + } + {this.props.advanced && + <> + this.setState({timezoneChanged: true, timezone: e.target.value})} /> +
@@ -131,12 +134,12 @@ export default class TimeSettings extends React.Component this.setTimeData({seconds: e.target.checked})} /> - Show Blinking Dots + Blinking Dots this.setTimeData({blink: e.target.checked})} /> - Show 24h Format - this.setTimeData({format24: e.target.checked})} /> + {this.props.dataService.data.time?.format24 ? "24" : "12"}h Format + this.setTimeData({format24: e.value})} /> @@ -145,9 +148,9 @@ export default class TimeSettings extends React.Component this.props.dataService.refresh().then(() => this.setState({deviceTimePulled: false}))) + .then(() => this.props.dataService.refreshTime()) .catch((e: APIError) => { if(this.props.toast) this.props.toast.show({ @@ -158,9 +161,9 @@ export default class TimeSettings extends React.Component this.props.dataService.refresh().then(() => this.setState({ntpServerChanged: false, deviceTimePulled: false}))) + .then(() => this.props.dataService.refreshTime().then(() => this.setState({ntpServer: undefined}))) .catch((e: APIError) => { if(this.props.toast) this.props.toast.show({ @@ -171,7 +174,7 @@ export default class TimeSettings extends React.Component this.props.dataService.refresh().then(() => this.setState({deviceTimeChanged: false, deviceTimePulled: false}))) + .then(() => this.props.dataService.refreshTime().then(() => this.setState({deviceTime: undefined}))) .catch((e: APIError) => { if(this.props.toast) this.props.toast.show({ @@ -194,10 +197,13 @@ export default class TimeSettings extends React.Component this.props.dataService.refresh().then(() => this.setState({timezone: timezone, deviceTimePulled: false}))) + private setTimezone(timezone: string) { + if(timezone.indexOf("%") !== -1) + localStorage.setItem("selectedTimezone", timezone); + else + localStorage.removeItem("selectedTimezone"); + this.props.dataService.requestDevice("POST", "/api/time", {timezone: timezone.indexOf("%") !== -1 ? timezone.substring(timezone.indexOf("%") + 1, timezone.length) : timezone}) + .then(() => this.props.dataService.refreshTime().then(() => this.setState({timezone: timezone, timezoneChanged: false}))) .catch((e: APIError) => { if(this.props.toast) this.props.toast.show({ diff --git a/pixelart-interface/src/components/pages/Settings/WifiSettings.tsx b/pixelart-interface/src/components/pages/Settings/WifiSettings.tsx index 3476bf4..dc94e8c 100644 --- a/pixelart-interface/src/components/pages/Settings/WifiSettings.tsx +++ b/pixelart-interface/src/components/pages/Settings/WifiSettings.tsx @@ -1,36 +1,303 @@ import DataService from '../../../services/DataService'; import React from 'react'; import { Toast } from 'primereact/toast'; +import { IoWarningOutline } from 'react-icons/io5'; +import { Messages } from 'primereact/messages'; +import { Link } from 'react-router-dom'; +import { Button } from 'primereact/button'; +import { InputText } from 'primereact/inputtext'; +import { SelectButton } from 'primereact/selectbutton'; +import { SelectItemOptionsType } from 'primereact/selectitem'; +import { BiHide, BiShow, BiWifi, BiWifi0, BiWifi1, BiWifi2, BiWifiOff } from 'react-icons/bi'; +import { FaLock, FaLockOpen } from 'react-icons/fa'; +import { ListBox } from 'primereact/listbox'; +import { asyncTimeout, validateSSID, validateWPA } from '../../../services/Helper'; +import { IWifiNetwork } from '../../../models/Settings'; +import { APIError } from '../../../models/Errors'; export interface IWifiSettingsComponentProps { dataService: DataService; toast: Toast | null; + advanced: boolean; } interface IWifiSettingsComponentState { - + ap?: boolean; + apSSID?: string; + apPassword?: string; + connect?: boolean; + ssid?: string; + password?: string; + showApPassword: boolean; + showPassword: boolean; + scanning: boolean; + applying: boolean; } +const switchButtonOptions: SelectItemOptionsType = [{label: "Off", value: false}, {label: "On", value: true}]; + export default class WifiSettings extends React.Component { + private messages: Messages | null = null; + constructor(props: IWifiSettingsComponentProps) { super(props); this.state = { - } + showApPassword: false, + showPassword: false, + scanning: false, + applying: false + }; } + componentDidMount(): void { + if(!this.props.dataService.dismissedWifiWarning && this.messages) + this.messages.show({ + closable: true, + sticky: true, + severity: "warn", + content: <> + +
+

+ Your device may disconnect temporarily on applying settings or searching for wifi networks. Depending on the settings you choose and the way you are connected to your device, you might loose connection permanently. +
+ Reenableing WiFi connection on your device is described under 'WiFi' on the 'Home' page. +

+
+ , + }); + } + public render() { + + const validSSID: boolean = !this.state.ssid || validateSSID(this.state.ssid); + const validApSSID: boolean = !this.state.apSSID || validateSSID(this.state.apSSID); + const validPassword: boolean = !this.state.password || validateWPA(this.state.password); + const validApPassword: boolean = !this.state.apPassword || validateWPA(this.state.apPassword); + return(
- TBD Wifi settings + this.messages = el} style={{textAlign: 'left'}} onRemove={() => this.props.dataService.dismissWifiWarning()} /> + +
+
Connect to WiFi Network
+
+ {this.setState({connect: e.value})}} options={switchButtonOptions}/> +
+
+ + {(this.state.connect !== undefined ? this.state.connect : this.props.dataService.data.wifi?.connect) && + <> + +
+
WiFi Network
+ {!this.props.advanced && + <> +
+ +
+
{item.rssi > -40 ? : item.rssi > -55 ? : item.rssi > -70 ? : item.rssi > -80 ? : }
+
{item.encryption > 0 ? 2 ? "var(--text-color)" : "var(--red-700)"}}/> : }
+
{item.ssid}
+
+ } + emptyMessage="No networks found" + onChange={(e) => this.setState({ssid: e.target.value})} + /> +
+
+
+
+ + } + {this.props.advanced && +
+ this.setState({ssid: e.target.value})} /> +
+ } +
+ +
+
WiFi Password
+
+ this.setState({password: e.target.value})} /> + {this.state.showPassword + ? {this.setState({showPassword: false})}} /> + : {this.setState({showPassword: true})}} /> + } +
+
+ + } + + + +
+
Host WiFi Network
+
+ {this.setState({ap: e.value})}} options={switchButtonOptions}/> +
+
+ + {(this.state.ap !== undefined ? this.state.ap : this.props.dataService.data.wifi?.ap) && + <> +
+
Hosted WiFi Name
+
+ this.setState({apSSID: e.target.value})} /> +
+
+ +
+
Hosted WiFi Password
+
+ this.setState({apPassword: e.target.value})} /> + {this.state.showApPassword + ? {this.setState({showApPassword: false})}} /> + : {this.setState({showApPassword: true})}} /> + } +
+
+ + + } + + {this.props.advanced && + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hosted Network IP{this.props.dataService.data.wifi?.apIP}
Hostname{this.props.dataService.data.wifi?.hostname}
MAC-Address{this.props.dataService.data.wifi?.mac}
Connection Status{this.props.dataService.data.wifi?.setupComplete ? "Connected" : "Disconnected"}
Connected Network IP{this.props.dataService.data.wifi?.ip}
Connected Network RSSI{this.props.dataService.data.wifi?.rssi}
+ } + +
+
+
+
); } + + private async scanWifi() { + this.setState({ + scanning: true + }); + + //first trigger scan, second time collect results + await this.props.dataService.scanWifi(); + asyncTimeout(10000); + await this.props.dataService.scanWifi(); + + this.setState({ + scanning: false + }); + } + + private applyWifiSettings() { + this.setState({ + applying: true + }); + + let wifiSettings: any = { + ap: this.state.ap, + apSSID: this.state.apSSID, + apPassword: this.state.apPassword, + connect: this.state.connect, + ssid: this.state.ssid, + password: this.state.password, + }; + + //delete undefined values + for(let setting in wifiSettings) { + if(wifiSettings[setting] === undefined || wifiSettings[setting] === "") + delete wifiSettings[setting]; + } + + this.props.dataService.requestDevice("POST", "/api/wifi", wifiSettings) + .then(async () => { + await asyncTimeout(10000); + await this.props.dataService.refreshWifi().catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + this.setState({ + ap: undefined, + apSSID: undefined, + apPassword: undefined, + connect: undefined, + ssid: undefined, + password: undefined, + applying: false + }); + }) + .catch((e: APIError) => { + if(this.props.toast) + this.props.toast.show({ + content: e.message, + severity: 'error', + closable: false + }); + }); + } } \ No newline at end of file diff --git a/pixelart-interface/src/components/shared/Sidebar.tsx b/pixelart-interface/src/components/shared/Sidebar.tsx index fa189d3..43ae2c3 100644 --- a/pixelart-interface/src/components/shared/Sidebar.tsx +++ b/pixelart-interface/src/components/shared/Sidebar.tsx @@ -19,7 +19,7 @@ export default class Sidebar extends React.Component { {process.env.REACT_APP_ENVIRONMENT !== "device" && <> - { this.props.dataService.setDeviceAddress(e.target.value)}} /> + { this.props.dataService.setDeviceAddress(e.target.value)}} onBlur={() => this.props.dataService.refresh() } /> } {this.props.model.map(cat => { diff --git a/pixelart-interface/src/index.tsx b/pixelart-interface/src/index.tsx index 4291e13..1bba138 100644 --- a/pixelart-interface/src/index.tsx +++ b/pixelart-interface/src/index.tsx @@ -1,14 +1,16 @@ import ReactDOM from 'react-dom/client'; import App from './App'; -import { HashRouter } from 'react-router-dom'; +import { HashRouter, useLocation } from 'react-router-dom'; import "primereact/resources/primereact.min.css"; import "primeicons/primeicons.css"; +const RouterWrapper = () => ; + const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( - + ); \ No newline at end of file diff --git a/pixelart-interface/src/models/Cache.ts b/pixelart-interface/src/models/Cache.ts new file mode 100644 index 0000000..042ba48 --- /dev/null +++ b/pixelart-interface/src/models/Cache.ts @@ -0,0 +1,9 @@ +//schema for cache item to save image creation history +export interface ICachedImage { + id: number; + name: string; + preview: string; + created: number; + animation: number[][][]; + image: number[]; +} \ No newline at end of file diff --git a/pixelart-interface/src/models/Settings.ts b/pixelart-interface/src/models/Settings.ts index 9a3158e..ddadf80 100644 --- a/pixelart-interface/src/models/Settings.ts +++ b/pixelart-interface/src/models/Settings.ts @@ -1,13 +1,23 @@ +//device api interfaces + export interface Wifi { - wifiAP: boolean; - wifiAPSSID: string; - wifiAPPassword: string; - wifiAPIP: string; - wifiConnect: boolean; - wifiSetupComplete: boolean; - wifiSSID: string; - wifiIP: string; - wifiHostname: string; + ap: boolean; + apSSID: string; + apPassword: string; + apIP: string; + connect: boolean; + setupComplete: boolean; + ssid: string; + ip: string; + hostname: string; + rssi: number; + mac: number; +} + +export interface IWifiNetwork { + ssid: string; + rssi: number; + encryption: number; } export interface Time { @@ -28,4 +38,41 @@ export interface Display { displayMode: number; brightness: number; version: string; + freeMemory: number; + freeSPIMemory: number; + refreshRate: number; + animation: boolean; + animationTime: number; + diashow: boolean; + diashowTime: number; + diashowModes: boolean; +} + +export interface Images { + displayImage: number; + imageNumber: number; + imagePrefixMax: number; + imageLoaded: boolean; + images: IImageData[]; +} + +export interface IImageData { + prefix: number; + folder: string; + animated: boolean; + rename?: string; + delete?: boolean; +} + +export interface IImageOperation { + src: string; + dst?: string +} + +export interface Socials { + request: string; + server: string; + apiKey: string; + displayChannel: number; + channelNumber: number; } \ No newline at end of file diff --git a/pixelart-interface/src/models/Socials.ts b/pixelart-interface/src/models/Socials.ts new file mode 100644 index 0000000..a243578 --- /dev/null +++ b/pixelart-interface/src/models/Socials.ts @@ -0,0 +1,9 @@ +export interface SocialItem { + id?: string; + delete?: boolean; + t: string; + c: string; + d: string; + f: string; + v: string; +} \ No newline at end of file diff --git a/pixelart-interface/src/models/Statistics.ts b/pixelart-interface/src/models/Statistics.ts new file mode 100644 index 0000000..a73873f --- /dev/null +++ b/pixelart-interface/src/models/Statistics.ts @@ -0,0 +1,7 @@ +import moment from "moment"; + +export interface IStatistics { + internal: number; + external: number; + time: moment.Moment; +} \ No newline at end of file diff --git a/pixelart-interface/src/models/Status.ts b/pixelart-interface/src/models/Status.ts index ec91b39..33f1ff9 100644 --- a/pixelart-interface/src/models/Status.ts +++ b/pixelart-interface/src/models/Status.ts @@ -1,3 +1,4 @@ +//connection state for pixelart device export enum Status { pending, disconnected, diff --git a/pixelart-interface/src/models/Version.ts b/pixelart-interface/src/models/Version.ts new file mode 100644 index 0000000..85c003d --- /dev/null +++ b/pixelart-interface/src/models/Version.ts @@ -0,0 +1,6 @@ +export interface VersionDetails { + version: string, + type: string, + environment?: string; + files: string[] +} \ No newline at end of file diff --git a/pixelart-interface/src/services/DataService.ts b/pixelart-interface/src/services/DataService.ts index e0e5014..d3cbddc 100644 --- a/pixelart-interface/src/services/DataService.ts +++ b/pixelart-interface/src/services/DataService.ts @@ -1,21 +1,34 @@ import axios, { AxiosResponse, AxiosError } from "axios"; +import moment from "moment"; import { APIError, APIErrorType } from "../models/Errors"; -import { Display, Time, Wifi } from "../models/Settings"; +import { Display, Images, Socials, Time, Wifi, IWifiNetwork } from "../models/Settings"; +import { IStatistics } from "../models/Statistics"; import { Status } from "../models/Status"; +import { VersionDetails } from "../models/Version"; export default class DataService { private deviceAddress: string = process.env.REACT_APP_ENVIRONMENT !== "device" ? "" : window.location.host; private darkTheme: boolean = true; + public dismissedWifiWarning: boolean = false; private status: Status = Status.pending; private apiKey: string | undefined; private authInterval: NodeJS.Timer | undefined; public onChanged: () => void; public data: { + wifiScan: IWifiNetwork[], wifi?: Wifi, time?: Time, - display?: Display - } = {}; + display?: Display, + socials?: Socials, + images?: Images, + } = { + wifiScan: [] + }; + public statistics: IStatistics[] = []; + public deviceWebinterfaceVersion?: VersionDetails; + public newestFirmware?: VersionDetails; + public newestWebinterface?: VersionDetails; constructor(onChanged: () => void) { this.onChanged = onChanged; @@ -24,6 +37,10 @@ export default class DataService { if(theme && theme === "light") this.darkTheme = false; + let dismissedWifiWarning: string | null = localStorage.getItem("dismissedWifiWarning"); + if(dismissedWifiWarning === "true") + this.dismissedWifiWarning = true; + if(process.env.REACT_APP_ENVIRONMENT) { let deviceAddress: string | null = localStorage.getItem("deviceAddress"); if(deviceAddress && deviceAddress.length > 3) @@ -34,9 +51,31 @@ export default class DataService { if(apiKey) this.apiKey = apiKey; + this.checkUpdateVersions(); this.refresh(); } + public async checkUpdateVersions() { + axios.get(process.env.REACT_APP_UPDATE_SERVER + "/webinterface/version.json") + .then(resp => { + if(resp.status === 200 && resp.data.version && resp.data.files) + this.newestWebinterface = resp.data; + }) + .catch(() => {}); + + axios.get(process.env.REACT_APP_UPDATE_SERVER + "/firmware/version.json") + .then(resp => { + if(resp.status === 200 && resp.data.version && resp.data.files) + this.newestFirmware = resp.data; + }) + .catch(() => {}); + } + + public async dismissWifiWarning() { + localStorage.setItem("dismissedWifiWarning", "true"); + this.dismissedWifiWarning = true; + } + public getDeviceAddress() { return this.deviceAddress; } @@ -64,33 +103,141 @@ export default class DataService { this.onChanged(); } - public async refresh() { + public async refresh(ignoreError: boolean = false) { + await this.refreshDisplay(ignoreError); + await this.refreshWifi(ignoreError); + await this.refreshTime(ignoreError); + await this.refreshSocials(ignoreError); + await this.refreshImages(ignoreError); + await this.refreshWebinterfaceVersion(); + } + + public async refreshWebinterfaceVersion(ignoreError: boolean = false) { + await this.requestDevice("GET", "/webinterface/version.json") + .then(resp => { + if(resp.files && resp.type && resp.version) + this.deviceWebinterfaceVersion = resp; + }) + .catch(() => {}); + + this.onChanged(); + } + + public async refreshDisplay(ignoreError: boolean = false) { + await this.requestDevice("GET", "/api/display") + .then(resp => { + this.data.display = resp + }) + .catch((e: APIError) => { + if(e.type !== APIErrorType.UnauthorizedError && e.type !== APIErrorType.AuthPending && !ignoreError) + this.setStatus(Status.disconnected); + }); + if(this.data.display) + this.statistics.push({ + internal: this.data.display.freeMemory, + external: this.data.display.freeSPIMemory, + time: moment() + }); + + this.onChanged(); + } + + public async refreshWifi(ignoreError: boolean = false) { await this.requestDevice("GET", "/api/wifi") .then(resp => { this.data.wifi = resp }) .catch((e: APIError) => { - if(e.type !== APIErrorType.UnauthorizedError && e.type !== APIErrorType.AuthPending) + if(e.type !== APIErrorType.UnauthorizedError && e.type !== APIErrorType.AuthPending && !ignoreError) this.setStatus(Status.disconnected); }); + this.onChanged(); + } + public async refreshTime(ignoreError: boolean = false) { await this.requestDevice