From 3b8e5a2f5a6e8beaf36b9191b4cd7538a344e1a2 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 4 Jul 2022 20:41:02 -0700 Subject: [PATCH 001/263] Initial port of Tubes - palettes and noise functions --- package-lock.json | 2985 ++++++++++++++++++++++++++++++++++++++++++++- wled00/FX.cpp | 43 + wled00/FX.h | 148 ++- wled00/html_ui.h | 1426 +++++++++++----------- wled00/palettes.h | 2118 +++++++++++++++++++++++++------- wled00/wled.h | 4 + 6 files changed, 5539 insertions(+), 1185 deletions(-) diff --git a/package-lock.json b/package-lock.json index 80b11afe87..2786f23ac8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2943 @@ { "name": "wled", "version": "0.13.2-a0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "wled", + "version": "0.13.2-a0", + "license": "ISC", + "dependencies": { + "clean-css": "^4.2.3", + "html-minifier-terser": "^5.1.1", + "inliner": "^1.13.1", + "nodemon": "^2.0.4", + "zlib": "^1.0.5" + } + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dependencies": { + "string-width": "^3.0.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "node_modules/boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/cheerio": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", + "integrity": "sha512-Fwcm3zkR37STnPC8FepSHeSYJM5Rd596TZOcfDUdojR4Q735aK1Xn+M+ISagNneuCwMjK28w4kX+ETILGNT/UQ==", + "dependencies": { + "css-select": "~1.0.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "~3.8.1", + "lodash": "^3.2.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/chokidar": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", + "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "node_modules/clap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", + "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", + "dependencies": { + "chalk": "^1.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", + "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/coa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "dependencies": { + "q": "^1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/configstore": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", + "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=", + "dependencies": { + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "object-assign": "^4.0.1", + "os-tmpdir": "^1.0.0", + "osenv": "^0.1.0", + "uuid": "^2.0.1", + "write-file-atomic": "^1.1.2", + "xdg-basedir": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/configstore/node_modules/uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details." + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-select": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", + "integrity": "sha512-/xPlD7betkfd7ChGkLGGWx5HWyiHDOSn7aACLzdH0nwucPvB0EAm8hMBm7Xn7vGfAeRRN7KZ8wumGm8NoNcMRw==", + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "1.0", + "domutils": "1.4", + "nth-check": "~1.0.0" + } + }, + "node_modules/css-what": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", + "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=", + "engines": { + "node": "*" + } + }, + "node_modules/csso": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-2.0.0.tgz", + "integrity": "sha1-F4tDpEYhIhwndWCG9THgL0KQDug=", + "dependencies": { + "clap": "^1.0.9", + "source-map": "^0.5.3" + }, + "bin": { + "csso": "bin/csso" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/dot-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", + "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", + "dependencies": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "node_modules/dot-case/node_modules/lower-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", + "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", + "dependencies": { + "tslib": "^1.10.0" + } + }, + "node_modules/dot-case/node_modules/no-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", + "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", + "dependencies": { + "lower-case": "^2.0.1", + "tslib": "^1.10.0" + } + }, + "node_modules/dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/es6-promise": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", + "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=" + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "dependencies": { + "ini": "^1.3.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/got": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", + "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", + "dependencies": { + "duplexify": "^3.2.0", + "infinity-agent": "^2.0.0", + "is-redirect": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "nested-error-stacks": "^1.0.0", + "object-assign": "^3.0.0", + "prepend-http": "^1.0.0", + "read-all-stream": "^3.0.0", + "timed-out": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/got/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "dependencies": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/html-minifier-terser/node_modules/camel-case": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", + "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", + "dependencies": { + "pascal-case": "^3.1.1", + "tslib": "^1.10.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/html-minifier-terser/node_modules/param-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", + "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", + "dependencies": { + "dot-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "node_modules/htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dependencies": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/infinity-agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", + "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inliner": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/inliner/-/inliner-1.13.1.tgz", + "integrity": "sha512-yoS+56puOu+Ug8FBRtxtTFnEn2NHqFs8BNQgSOvzh3J0ommbwNw8VKiaVNYjWK6fgPuByq95KyV0LC+qV9IwLw==", + "dependencies": { + "ansi-escapes": "^1.4.0", + "ansi-styles": "^2.2.1", + "chalk": "^1.1.3", + "charset": "^1.0.0", + "cheerio": "^0.19.0", + "debug": "^2.2.0", + "es6-promise": "^2.3.0", + "iconv-lite": "^0.4.11", + "jschardet": "^1.3.0", + "lodash.assign": "^3.2.0", + "lodash.defaults": "^3.1.2", + "lodash.foreach": "^3.0.3", + "mime": "^1.3.4", + "minimist": "^1.1.3", + "request": "^2.74.0", + "svgo": "^0.6.6", + "then-fs": "^2.0.0", + "uglify-js": "^2.8.0", + "update-notifier": "^0.5.0" + }, + "bin": { + "inliner": "cli/index.js" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dependencies": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/js-yaml": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", + "integrity": "sha512-BLv3oxhfET+w5fjPwq3PsAsxzi9i3qzU//HMpWVz0A6KplF86HdR9x2TGnv9DXhSUrO7LO8czUiTd3yb3mLSvg==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/jschardet": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.6.0.tgz", + "integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/latest-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz", + "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=", + "dependencies": { + "package-json": "^1.0.0" + }, + "bin": { + "latest-version": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" + }, + "node_modules/lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" + }, + "node_modules/lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "node_modules/lodash._baseeach": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", + "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", + "dependencies": { + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "node_modules/lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "dependencies": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "node_modules/lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "dependencies": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash.defaults": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", + "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", + "dependencies": { + "lodash.assign": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash.foreach": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", + "integrity": "sha1-b9fvt5aRrs1n/erCdhyY5wHWw5o=", + "dependencies": { + "lodash._arrayeach": "^3.0.0", + "lodash._baseeach": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dependencies": { + "mime-db": "1.44.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/nested-error-stacks": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", + "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=", + "dependencies": { + "inherits": "~2.0.1" + } + }, + "node_modules/nodemon": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", + "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==", + "hasInstallScript": true, + "dependencies": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^4.0.0" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nodemon/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/chalk/node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/nodemon/node_modules/is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nodemon/node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/nodemon/node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon/node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/update-notifier": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", + "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", + "dependencies": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/nodemon/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/nodemon/node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz", + "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=", + "dependencies": { + "got": "^3.2.0", + "registry-url": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pascal-case": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz", + "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", + "dependencies": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "node_modules/pascal-case/node_modules/lower-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", + "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", + "dependencies": { + "tslib": "^1.10.0" + } + }, + "node_modules/pascal-case/node_modules/no-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", + "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", + "dependencies": { + "lower-case": "^2.0.1", + "tslib": "^1.10.0" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", + "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "dependencies": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-all-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/read-all-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/read-all-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/read-all-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/registry-auth-token": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", + "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/repeating": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "dependencies": { + "is-finite": "^1.0.0" + }, + "bin": { + "repeating": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dependencies": { + "semver": "^5.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "engines": { + "node": "*" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", + "dependencies": { + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.6.6.tgz", + "integrity": "sha512-C5A1r5SjFesNoKsmc+kWBxmB04iBGH2D/nFy8HJaME9+SyZKcmqcN8QG+GwxIc7D2+JWhaaW7uaM9+XwfplTEQ==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dependencies": { + "coa": "~1.0.1", + "colors": "~1.1.2", + "csso": "~2.0.0", + "js-yaml": "~3.6.0", + "mkdirp": "~0.5.1", + "sax": "~1.2.1", + "whet.extend": "~0.9.9" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/term-size": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/then-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", + "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", + "dependencies": { + "promise": ">=3.2 <8" + } + }, + "node_modules/timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "node_modules/undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dependencies": { + "debug": "^2.2.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz", + "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=", + "dependencies": { + "chalk": "^1.0.0", + "configstore": "^1.0.0", + "is-npm": "^1.0.0", + "latest-version": "^1.0.0", + "repeating": "^1.1.2", + "semver-diff": "^2.0.0", + "string-length": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/url-parse-lax/node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "engines": { + "node": ">=4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/whet.extend": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", + "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", + "dependencies": { + "os-homedir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "node_modules/zlib": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", + "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=", + "hasInstallScript": true, + "engines": { + "node": ">=0.2.0" + } + } + }, "dependencies": { "@sindresorhus/is": { "version": "0.14.0", @@ -57,9 +2992,9 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "string-width": { "version": "3.1.0", @@ -312,7 +3247,7 @@ "cheerio": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", - "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "integrity": "sha512-Fwcm3zkR37STnPC8FepSHeSYJM5Rd596TZOcfDUdojR4Q735aK1Xn+M+ISagNneuCwMjK28w4kX+ETILGNT/UQ==", "requires": { "css-select": "~1.0.0", "dom-serializer": "~0.1.0", @@ -466,7 +3401,7 @@ "css-select": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", - "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "integrity": "sha512-/xPlD7betkfd7ChGkLGGWx5HWyiHDOSn7aACLzdH0nwucPvB0EAm8hMBm7Xn7vGfAeRRN7KZ8wumGm8NoNcMRw==", "requires": { "boolbase": "~1.0.0", "css-what": "1.0", @@ -965,7 +3900,7 @@ "inliner": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/inliner/-/inliner-1.13.1.tgz", - "integrity": "sha1-5QApgev1Dp2fMTcRSBz/Ei1PP8s=", + "integrity": "sha512-yoS+56puOu+Ug8FBRtxtTFnEn2NHqFs8BNQgSOvzh3J0ommbwNw8VKiaVNYjWK6fgPuByq95KyV0LC+qV9IwLw==", "requires": { "ansi-escapes": "^1.4.0", "ansi-styles": "^2.2.1", @@ -1094,7 +4029,7 @@ "js-yaml": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", - "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", + "integrity": "sha512-BLv3oxhfET+w5fjPwq3PsAsxzi9i3qzU//HMpWVz0A6KplF86HdR9x2TGnv9DXhSUrO7LO8czUiTd3yb3mLSvg==", "requires": { "argparse": "^1.0.7", "esprima": "^2.6.0" @@ -1116,9 +4051,9 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" }, "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-traverse": { "version": "0.4.1", @@ -1131,13 +4066,13 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" } }, @@ -1173,7 +4108,7 @@ "lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" }, "lodash._arrayeach": { "version": "3.0.0", @@ -1981,6 +4916,11 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -2000,9 +4940,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "emoji-regex": { "version": "8.0.0", @@ -2024,11 +4964,6 @@ } } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -2050,7 +4985,7 @@ "svgo": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.6.6.tgz", - "integrity": "sha1-s0CIkDbyD5tEdUMHfQ9Vc+0ETAg=", + "integrity": "sha512-C5A1r5SjFesNoKsmc+kWBxmB04iBGH2D/nFy8HJaME9+SyZKcmqcN8QG+GwxIc7D2+JWhaaW7uaM9+XwfplTEQ==", "requires": { "coa": "~1.0.1", "colors": "~1.1.2", diff --git a/wled00/FX.cpp b/wled00/FX.cpp index fa6cb35513..af5ec9a9d8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4225,5 +4225,48 @@ uint16_t WS2812FX::mode_aurora(void) { setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2]); } + return FRAMETIME; +} + + + + +#define MAX_VIRTUAL_LEDS 150 +uint8_t noise[MAX_VIRTUAL_LEDS]; + +void fillnoise8(uint32_t frame, uint8_t num_leds) { + uint16_t scale = 17; + uint8_t dataSmoothing = 240; + + for (int i = 0; i < num_leds; i++) { + uint8_t data = inoise8(i * scale, frame>>2); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + data = qsub8(data,16); + data = qadd8(data,scale8(data,39)); + + uint8_t olddata = noise[i]; + uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); + noise[i] = newdata; + } +} + +uint16_t WS2812FX::mode_tubes_moise(void) { + uint16_t pixelLen = SEGLEN > MAX_VIRTUAL_LEDS ? MAX_VIRTUAL_LEDS : SEGLEN; + // uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 + // if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + // uint32_t* pixels = reinterpret_cast(SEGENV.data); + uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); + uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; + + // generate noise data + fillnoise8(now>>4, pixelLen); + + uint16_t offset = 0; + for (int i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(noise[i], true, PALETTE_SOLID_WRAP, 0)); + } + return FRAMETIME; } \ No newline at end of file diff --git a/wled00/FX.h b/wled00/FX.h index aeefee35eb..98123f5a22 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -119,7 +119,7 @@ #define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) #define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED ) -#define MODE_COUNT 118 +#define MODE_COUNT 119 #define FX_MODE_STATIC 0 #define FX_MODE_BLINK 1 @@ -240,6 +240,8 @@ #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 +#define FX_MODE_TUBES_NOISE 118 + class WS2812FX { typedef uint16_t (WS2812FX::*mode_ptr)(void); @@ -609,6 +611,7 @@ class WS2812FX { _mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends; _mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator; _mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth; + _mode[FX_MODE_TUBES_NOISE] = &WS2812FX::mode_tubes_moise; _brightness = DEFAULT_BRIGHTNESS; currentPalette = CRGBPalette16(CRGB::Black); @@ -833,7 +836,8 @@ class WS2812FX { mode_candy_cane(void), mode_blends(void), mode_tv_simulator(void), - mode_dynamic_smooth(void); + mode_dynamic_smooth(void), + mode_tubes_moise(void); private: uint32_t crgb_to_col(CRGB fastled); @@ -936,19 +940,143 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", "Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", "Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", -"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth" +"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth", +"My Tubes" ])====="; +// "Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", +// "Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", +// "Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", +// "Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", +// "Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", +// "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", +// "Candy2" const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", -"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", -"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", -"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", -"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", -"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", -"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", -"Candy2" +"Forest","Rainbow","Rainbow Bands", + +"ib_jul01", +"es_vintage_57", +"es_vintage_01", +"es_rivendell_15", +"rgi_15", +"retro2_16", +"Analogous_1", +"es_pinksplash_08", +"es_pinksplash_07", +"Coral_reef", + +"es_ocean_breeze_068", +"es_ocean_breeze_036", +"departure", +"es_landscape_64", +"es_landscape_33", +"rainbowsherbet", +"gr65_hult", +"gr64_hult", +"GMT_drywet", +"ib15", + +"Fuschia_7", +"es_emerald_dragon_08", +"lava", +"fire", +"haiyan_23", +"Colorfull", +"Magenta_Evening", +"Pink_Purple", +"Sunset_Real", +"es_autumn_19", + +"BlacK_Blue_Magenta_White", +"BlacK_Magenta_Red", +"BlacK_Red_Magenta_Yellow", +"Blue_Cyan_Yellow", +"Sunset_Yellow", +"cloud", +"fireandice", +"bhw2_39", +"rainfall", +"tashangel", + +"butterflytalker", +"os250k_metres", +"Night_Midnight", +"Afterdusk", +"BlueSky", +"Gold_Orange", +"frizzell_05", +"frizzell_09", +"frizzell_10", +"frizzell_12", + +"fib53_01", +"fib53_18", +"fib53_07", +"fib53_13", +"fib53_17", +"fib53_05", +"Analogous_02", +"Analogous_04a", +"Cyan_Orange_Stripped", +"Cyan_White_Green", + +"Wild_Orange", +"IKat_Radial", +"Citrus", +"Teal_Blue", +"Ldby_Orange", +"purple_orange_d07", +"blue_tan_d08", +"green_purple_d07", +"knoza_00", +"knoza_18", + +"calpan_18", +"calbayo_18", +"fib53_15", +"grindylow_15", +"grindylow_21", +"konjo_08", +"konjo_18", +"konjo_19", +"konkikyo_19", +"mccahon_16", +"sulz_10", +"sulz_12", +"sulz_15", +"sulz_21", +"sulz_22", +"Pills_2", +"Pink_Yellow_Orange_1", +"es_autumn_04", +"es_autumn_02", +"es_candide_30", +"es_chic_16", +"es_coffee_01", +"es_emerald_dragon_01", +"es_landscape_57", +"es_landscape_22", +"es_landscape_47", +"es_landscape_10", +"es_landscape_76", +"es_landscape_61", +"es_landscape_60", +"es_landscape_51", +"es_landscape_06", +"es_ocean_breeze_049", +"es_ocean_breeze_057", +"es_ocean_breeze_074", +"es_pinksplash_05", +"es_pinksplash_10", +"es_vintage_56", +"es_vintage_10", +"gold_yellow", +"radioactive_slime", +"pastel_rainbow", +"purple_sunset", +"janico_22" ])====="; #endif diff --git a/wled00/html_ui.h b/wled00/html_ui.h index b24a0f140e..2912817640 100644 --- a/wled00/html_ui.h +++ b/wled00/html_ui.h @@ -7,9 +7,9 @@ */ // Autogenerated from wled00/data/index.htm, do not edit!! -const uint16_t PAGE_index_L = 35625; +const uint16_t PAGE_index_L = 35617; const uint8_t PAGE_index[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xcc, 0xbd, 0x77, 0x7f, 0xe3, 0xb8, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xcc, 0xbd, 0x77, 0x7f, 0xe3, 0xb8, 0xae, 0x30, 0xfc, 0x7f, 0x3e, 0x85, 0xc7, 0xb3, 0x3b, 0x6b, 0x8d, 0x65, 0x59, 0xee, 0x6d, 0x34, 0xb9, 0xe9, 0xbd, 0x3a, 0x3d, 0x9b, 0x7b, 0x1e, 0x49, 0xa6, 0x6d, 0x25, 0xb2, 0xe4, 0x48, 0x72, 0x8b, 0xe3, 0xf7, 0xb3, 0xbf, 0x00, 0x49, 0x35, 0x97, 0x24, 0x7b, 0xce, 0xbd, 0xcf, 0xef, 0xd9, @@ -1525,715 +1525,715 @@ const uint8_t PAGE_index[] PROGMEM = { 0xf4, 0x6a, 0x1f, 0x8d, 0x81, 0x6f, 0xc0, 0x7f, 0xb6, 0xc7, 0x9f, 0xf1, 0x13, 0x6c, 0x7b, 0x20, 0x7e, 0xd9, 0x55, 0x94, 0x5b, 0x78, 0xf0, 0xde, 0x66, 0xa2, 0xa9, 0x05, 0x07, 0x7b, 0x88, 0x92, 0x4a, 0x88, 0x05, 0x77, 0x51, 0x7a, 0x9b, 0x73, 0x3f, 0xc5, 0x98, 0x3b, 0x25, 0x59, 0xe1, 0x1d, - 0x9c, 0x32, 0x78, 0xfb, 0xdf, 0xc5, 0x5e, 0xad, 0x7f, 0x24, 0x96, 0x65, 0x39, 0xa7, 0xe1, 0x1d, - 0x0e, 0x21, 0x94, 0x9d, 0x60, 0x16, 0x65, 0xeb, 0x0a, 0x44, 0x05, 0x42, 0x4d, 0x84, 0xb1, 0x4e, - 0xad, 0x54, 0xec, 0x38, 0x2c, 0x62, 0xe2, 0x99, 0x98, 0x22, 0xe8, 0x27, 0x31, 0x8a, 0x50, 0x8e, - 0x03, 0xa5, 0x24, 0xa8, 0x81, 0x36, 0x76, 0xeb, 0x3a, 0x49, 0xef, 0x81, 0x59, 0xa5, 0xe9, 0xd4, - 0x22, 0x84, 0x80, 0x3e, 0xcd, 0x62, 0xe7, 0x08, 0x55, 0xd5, 0x10, 0xd4, 0x5c, 0x0b, 0x74, 0x5a, - 0xa2, 0x13, 0x64, 0x70, 0xa2, 0x6c, 0x58, 0x8e, 0xeb, 0x68, 0x7c, 0x5b, 0x14, 0x74, 0x04, 0xd2, - 0x72, 0x8f, 0x8b, 0xc4, 0x82, 0xff, 0x76, 0x33, 0x1b, 0x74, 0x51, 0xa0, 0x81, 0xc9, 0x75, 0x60, - 0x0b, 0x22, 0xb4, 0x87, 0x40, 0x1d, 0x8f, 0x06, 0x6d, 0x1c, 0xed, 0xf1, 0xd6, 0xc3, 0x1d, 0xd7, - 0xa3, 0x85, 0xdc, 0x83, 0x37, 0x07, 0xcb, 0xd8, 0x70, 0x8d, 0x8b, 0x29, 0x13, 0x6c, 0x9d, 0xcf, - 0xe4, 0x5e, 0xb3, 0xcc, 0x43, 0x24, 0x2d, 0xfc, 0x22, 0x92, 0x8a, 0x51, 0x80, 0xf3, 0x17, 0x10, - 0xa6, 0xb8, 0xaf, 0x93, 0xf0, 0x8e, 0xc0, 0x2b, 0xe8, 0x80, 0x54, 0x09, 0xfa, 0x3c, 0xf4, 0xa5, - 0x0b, 0x50, 0x96, 0x92, 0x9f, 0x18, 0x93, 0xcd, 0x50, 0x14, 0x96, 0x37, 0xd4, 0xf0, 0x97, 0x67, - 0x5f, 0x01, 0x39, 0x1f, 0xcd, 0x3c, 0x20, 0x40, 0x42, 0xd1, 0x56, 0x20, 0x7e, 0x07, 0xc9, 0xb0, - 0xac, 0x28, 0x18, 0x87, 0xbd, 0x27, 0x48, 0x87, 0xeb, 0x94, 0x03, 0x20, 0x74, 0xa2, 0xe1, 0x0b, - 0xa1, 0x81, 0xbe, 0x58, 0x90, 0x06, 0xba, 0x34, 0xaa, 0x5d, 0xb8, 0x1e, 0x57, 0x56, 0x8b, 0x56, - 0x1d, 0xd4, 0x01, 0x5d, 0xe2, 0xfa, 0xe9, 0xa9, 0x2a, 0xd8, 0x78, 0x4a, 0x37, 0xd5, 0x94, 0x53, - 0x7e, 0x13, 0xcf, 0xbc, 0xea, 0xd4, 0x5a, 0x94, 0x53, 0x5d, 0xd0, 0xc4, 0xd9, 0xeb, 0xca, 0x2a, - 0xe6, 0xd9, 0xd2, 0xb6, 0x33, 0xf7, 0xb8, 0x2a, 0x95, 0x50, 0x17, 0x9e, 0x86, 0xf7, 0x3a, 0xa5, - 0x04, 0xc7, 0x80, 0x8f, 0x60, 0xf8, 0x0e, 0x6d, 0x53, 0x1c, 0x9a, 0xcc, 0xfa, 0x91, 0xfd, 0xe5, - 0x53, 0x70, 0x76, 0x5e, 0xde, 0x9d, 0xca, 0xc1, 0xfd, 0xe5, 0x93, 0x62, 0x5d, 0xc0, 0x1e, 0x60, - 0x91, 0xa1, 0xa2, 0xdc, 0xff, 0xbe, 0xbb, 0x60, 0x48, 0xa6, 0x99, 0xa4, 0x3c, 0x7e, 0x67, 0x60, - 0x0f, 0xff, 0x82, 0xf7, 0xef, 0xf4, 0xb6, 0x8f, 0xf6, 0x32, 0xd8, 0xf6, 0x7c, 0xed, 0x7d, 0x53, - 0x47, 0x80, 0x8e, 0x28, 0x50, 0x1f, 0xba, 0xb8, 0xa8, 0x50, 0xf0, 0xc3, 0x43, 0x8e, 0x14, 0x6c, - 0x65, 0x79, 0x91, 0xdb, 0x44, 0xfc, 0x70, 0xce, 0x25, 0x67, 0xfe, 0xf9, 0xf2, 0x6b, 0x3c, 0xd6, - 0xa8, 0x9a, 0x24, 0x77, 0x8a, 0x1d, 0x17, 0x3f, 0xbb, 0x03, 0x14, 0x08, 0x92, 0xb3, 0xde, 0xf9, - 0x52, 0xd1, 0xf5, 0x85, 0xd7, 0xe9, 0x14, 0xc3, 0x57, 0xf4, 0x9a, 0x7d, 0x8f, 0x09, 0x5e, 0x01, - 0x2c, 0x17, 0xce, 0x60, 0x42, 0x90, 0x51, 0x25, 0x5e, 0xf3, 0x6c, 0x40, 0x9f, 0x93, 0x0c, 0x87, - 0xa6, 0xb3, 0x6c, 0xd8, 0x1a, 0x5f, 0x63, 0x63, 0x63, 0x68, 0x92, 0x42, 0xf5, 0x2d, 0x33, 0x02, - 0x7e, 0x76, 0x38, 0x11, 0x68, 0xa6, 0x93, 0xd5, 0x2c, 0xd7, 0x95, 0xa7, 0x15, 0x3f, 0x65, 0xfc, - 0x73, 0x49, 0xcf, 0x20, 0xbe, 0xcb, 0xd3, 0x10, 0x46, 0x33, 0xc7, 0x18, 0x04, 0x2e, 0xcc, 0x80, - 0xc2, 0x90, 0xa7, 0xf3, 0xc2, 0x99, 0xdc, 0x64, 0x27, 0xae, 0x7c, 0xbd, 0x9c, 0x39, 0xe3, 0x7b, - 0x16, 0x6f, 0x22, 0x86, 0x37, 0xe1, 0x08, 0x37, 0x90, 0x84, 0x5e, 0x07, 0x3f, 0x78, 0xa3, 0x5c, - 0x04, 0x28, 0xc4, 0x18, 0x0f, 0x56, 0x26, 0x4b, 0x41, 0x13, 0x2c, 0x2b, 0xa5, 0xdb, 0x18, 0xa3, - 0xc1, 0x06, 0xdc, 0x4e, 0x8a, 0x35, 0xe1, 0xec, 0x1b, 0x84, 0xb0, 0x83, 0x81, 0x46, 0x78, 0xb2, - 0x5d, 0xe6, 0x85, 0x68, 0xca, 0x54, 0xe8, 0x19, 0x05, 0xbe, 0xec, 0xca, 0xd1, 0xa0, 0xde, 0x30, - 0x5b, 0xa6, 0x36, 0xbc, 0x2e, 0x65, 0xfa, 0xc8, 0x22, 0xf2, 0xde, 0xac, 0x36, 0x89, 0x94, 0x40, - 0x88, 0x9c, 0xea, 0xa2, 0xde, 0xe9, 0x8b, 0x85, 0x21, 0x8c, 0x8c, 0xec, 0x2c, 0xc6, 0x94, 0xeb, - 0xc0, 0x85, 0x33, 0xfa, 0xbb, 0x6c, 0x21, 0x21, 0xb6, 0x74, 0xb1, 0xe7, 0x4a, 0xe3, 0xa3, 0x48, - 0xf4, 0xbb, 0x13, 0xe1, 0xd6, 0x23, 0xd6, 0x8a, 0x9f, 0x76, 0x86, 0xdb, 0xdf, 0x3d, 0xb0, 0xde, - 0xc1, 0xdb, 0xc1, 0xd1, 0x5e, 0x34, 0x44, 0x51, 0x7b, 0x29, 0x45, 0x2c, 0x50, 0xaa, 0xf9, 0xe0, - 0x38, 0x7f, 0xb6, 0x54, 0x17, 0x36, 0xef, 0x82, 0x51, 0x07, 0xd6, 0x2c, 0x9e, 0x83, 0x54, 0x26, - 0xdd, 0x96, 0xce, 0x58, 0xa7, 0xe7, 0xfb, 0xe7, 0xa0, 0xcc, 0x3d, 0x64, 0xd4, 0x1b, 0x5f, 0x97, - 0x9c, 0x5d, 0x32, 0xa8, 0x84, 0xbf, 0xe9, 0xe3, 0xe6, 0xad, 0x1c, 0xf9, 0x0b, 0x0c, 0x1c, 0x47, - 0x73, 0xf0, 0xf6, 0x90, 0x46, 0x23, 0x9f, 0xab, 0x2d, 0x0a, 0xf4, 0x03, 0x72, 0xbb, 0xec, 0x52, - 0xf5, 0x38, 0xd4, 0x07, 0xca, 0x3f, 0xe3, 0x66, 0xb8, 0x20, 0xdf, 0xa0, 0x4e, 0x67, 0x09, 0xc4, - 0xb0, 0xc9, 0x49, 0x50, 0x50, 0x7d, 0xe4, 0x08, 0xc4, 0xc4, 0x89, 0x07, 0x6f, 0x6f, 0xfb, 0xe2, - 0xaf, 0xd3, 0x7e, 0x9b, 0x8a, 0xe6, 0x21, 0x64, 0xe8, 0xae, 0x27, 0x29, 0x1b, 0xbd, 0x8d, 0x56, - 0xd4, 0xaf, 0xdd, 0xbc, 0x72, 0x72, 0x77, 0xa5, 0x25, 0x0a, 0x79, 0xbd, 0xa4, 0xee, 0x14, 0x78, - 0x4e, 0x7a, 0xa4, 0x62, 0x87, 0x52, 0x34, 0xea, 0x73, 0x0a, 0xce, 0xcf, 0xd2, 0xf3, 0x41, 0x65, - 0xe5, 0x41, 0xf4, 0xe7, 0x8b, 0x4a, 0x5f, 0x49, 0xbb, 0xe4, 0xb1, 0xa6, 0x06, 0x8b, 0xf5, 0xdd, - 0x81, 0x30, 0x81, 0xbd, 0x73, 0x5c, 0x4f, 0x67, 0x93, 0xf5, 0x3d, 0x8f, 0xfe, 0x70, 0xba, 0x1f, - 0x25, 0x61, 0xfc, 0xce, 0xe6, 0x8c, 0x5d, 0xb1, 0x30, 0xdb, 0xeb, 0xb1, 0x83, 0x01, 0xe8, 0xda, - 0xca, 0xab, 0xb1, 0xe7, 0x8a, 0xfd, 0xc9, 0xe8, 0xc2, 0xbd, 0x9b, 0xdd, 0xcf, 0x31, 0x47, 0xfd, - 0x67, 0x1e, 0x18, 0x31, 0xa1, 0x1e, 0xa7, 0xf6, 0x20, 0x1f, 0x42, 0xc3, 0x51, 0x1c, 0x38, 0x39, - 0xfc, 0x7f, 0x2f, 0x80, 0x2f, 0xae, 0xea, 0x02, 0x7e, 0x43, 0xfc, 0xff, 0x9e, 0x0b, 0xea, 0x9f, - 0xf5, 0xce, 0xee, 0xe7, 0xe4, 0xe3, 0x40, 0x75, 0x0f, 0xfd, 0x97, 0x9a, 0xaf, 0x64, 0xbe, 0x07, - 0x0a, 0x0d, 0x56, 0xba, 0x79, 0x67, 0xcb, 0x0d, 0xcc, 0xf9, 0x3b, 0xeb, 0xde, 0xd2, 0x0d, 0x09, - 0xf9, 0xa0, 0x9d, 0x81, 0x76, 0x76, 0x0e, 0xbc, 0xba, 0xc2, 0x61, 0xb0, 0x8e, 0x9b, 0x50, 0x58, - 0x42, 0x27, 0x90, 0xaa, 0x1c, 0x54, 0x25, 0x0b, 0x3d, 0x32, 0x8c, 0xbe, 0x59, 0xce, 0x9f, 0x90, - 0x06, 0xf6, 0x9f, 0x6e, 0x6f, 0xaf, 0xc2, 0xeb, 0x5b, 0x1b, 0xc4, 0x5d, 0x90, 0x93, 0xba, 0x64, - 0xba, 0xca, 0x7f, 0x8b, 0x8a, 0x2b, 0xc7, 0xf6, 0xbb, 0xbd, 0x83, 0xae, 0xcd, 0x03, 0x0b, 0xec, - 0x2f, 0xe9, 0x75, 0x84, 0xfc, 0xb8, 0x3b, 0x49, 0x44, 0xa4, 0xc1, 0x24, 0xe1, 0xfb, 0xfc, 0x0e, - 0x28, 0x17, 0xdb, 0x2e, 0x2d, 0xd8, 0xe6, 0xe9, 0x52, 0xc9, 0x7d, 0x47, 0x14, 0x37, 0xa7, 0xb6, - 0x19, 0x3c, 0xbb, 0xb0, 0x89, 0xaa, 0x5f, 0x2c, 0xc2, 0x25, 0xff, 0x2b, 0x07, 0x64, 0xbf, 0xbf, - 0x8d, 0x62, 0xbc, 0x28, 0xe9, 0xde, 0x61, 0x38, 0x70, 0xe5, 0xc7, 0x53, 0xd0, 0x28, 0x42, 0x42, - 0xea, 0x24, 0xa2, 0xc1, 0x6a, 0xf7, 0xd1, 0x2c, 0xea, 0xe6, 0x54, 0xde, 0xb1, 0xff, 0x60, 0x51, - 0x1c, 0x38, 0x95, 0xcd, 0x41, 0xff, 0xf0, 0x6c, 0x6b, 0xfa, 0xfe, 0xc6, 0xb5, 0x6b, 0x1d, 0xfd, - 0x9a, 0xa1, 0x55, 0x80, 0xfc, 0xfb, 0x75, 0x0b, 0x41, 0xf7, 0x96, 0xca, 0xdd, 0x5a, 0xfd, 0x1f, - 0x81, 0x2e, 0x2c, 0x24, 0x0c, 0x20, 0x13, 0xe8, 0xf4, 0xfa, 0xbd, 0xe8, 0x52, 0xaf, 0xf3, 0x21, - 0x2f, 0xd0, 0x46, 0x03, 0xe2, 0xbd, 0x70, 0x51, 0xb4, 0xbd, 0xb8, 0x5e, 0xeb, 0xc7, 0x39, 0x30, - 0xa1, 0xdc, 0xda, 0x43, 0xb5, 0x3b, 0x4d, 0x68, 0xa6, 0x44, 0x50, 0xb3, 0x2c, 0xaf, 0x3d, 0xf5, - 0xcf, 0xef, 0x8e, 0xad, 0x70, 0x3a, 0x9d, 0x53, 0x0a, 0x47, 0xd6, 0xbd, 0x09, 0x27, 0xf5, 0x81, - 0x45, 0x31, 0xcb, 0x1f, 0x73, 0xd8, 0x82, 0x58, 0x63, 0x96, 0x77, 0x6f, 0x3b, 0xf6, 0x1e, 0xae, - 0x01, 0x7c, 0x2c, 0x3a, 0x38, 0x52, 0x5c, 0x12, 0x33, 0x7f, 0xf5, 0x4b, 0x5e, 0x71, 0x8f, 0x57, - 0x72, 0x09, 0xc4, 0xb5, 0xd6, 0xf1, 0x87, 0xe4, 0x2e, 0x9a, 0xa7, 0xc9, 0x0d, 0xcd, 0x83, 0x75, - 0xc3, 0xf9, 0xe4, 0x8a, 0x8c, 0x13, 0xe8, 0x95, 0x32, 0x67, 0xf0, 0x91, 0x56, 0x3a, 0xbe, 0x8f, - 0x32, 0x74, 0x5d, 0xe2, 0xcd, 0x41, 0x00, 0xc6, 0x17, 0x0d, 0x0c, 0xaa, 0xce, 0x96, 0xae, 0xef, - 0x4c, 0x9e, 0x54, 0xdf, 0x90, 0xa7, 0xec, 0x92, 0x8c, 0x98, 0xfa, 0xa6, 0xa4, 0x33, 0x0d, 0x54, - 0x1e, 0xe1, 0xec, 0x32, 0xd0, 0xdd, 0x3b, 0x4d, 0x9f, 0x4e, 0xee, 0xca, 0x39, 0x28, 0xef, 0x0f, - 0xfd, 0x41, 0x7e, 0xe4, 0x20, 0x83, 0xba, 0xe4, 0x90, 0x43, 0x82, 0xcf, 0xe4, 0xc8, 0x67, 0x64, - 0x37, 0x9d, 0x8e, 0x48, 0xf0, 0x4d, 0xf5, 0xce, 0xf2, 0x73, 0x7a, 0x5e, 0xa8, 0x9d, 0xa1, 0x18, - 0x94, 0x3e, 0x10, 0xee, 0xaa, 0x67, 0xe1, 0x79, 0x10, 0x77, 0xd3, 0xc4, 0x0b, 0xd1, 0x14, 0x5d, - 0x3e, 0x9d, 0x78, 0xa8, 0x36, 0x98, 0xb0, 0xd3, 0x73, 0xbd, 0x70, 0x88, 0x23, 0xc2, 0x9f, 0x70, - 0x60, 0x61, 0xfd, 0x20, 0x84, 0x07, 0x22, 0xfd, 0xa3, 0xbb, 0xf0, 0x4d, 0x08, 0xea, 0x0d, 0xbb, - 0x1c, 0xd9, 0xd2, 0xdf, 0x95, 0x1f, 0x31, 0x92, 0xf1, 0x93, 0xac, 0x23, 0x9b, 0x91, 0x71, 0xc3, - 0xca, 0x27, 0x57, 0xd7, 0xb1, 0xac, 0x60, 0x6d, 0x27, 0xe3, 0x3c, 0x1b, 0xa8, 0xf3, 0x0c, 0x8d, - 0xd4, 0x16, 0xb7, 0x6e, 0x53, 0x6d, 0xcc, 0xd8, 0x5b, 0x9e, 0x2d, 0xe1, 0x12, 0x9e, 0x42, 0x87, - 0x15, 0x87, 0xd2, 0xc1, 0x87, 0xc2, 0x10, 0x1d, 0xfc, 0xc5, 0xb5, 0x61, 0x48, 0x08, 0x05, 0x10, - 0x8f, 0x78, 0x53, 0x31, 0x14, 0x75, 0x66, 0xe5, 0xc0, 0x83, 0x8d, 0x91, 0xdc, 0x84, 0x73, 0x3e, - 0x1a, 0x3c, 0xac, 0xf0, 0x57, 0x75, 0x5a, 0xd1, 0xa8, 0xe5, 0xb7, 0x96, 0x33, 0x76, 0xfe, 0x4f, - 0x63, 0x60, 0xf4, 0xbd, 0x3c, 0x45, 0x09, 0x38, 0xa5, 0x86, 0xfa, 0x59, 0x5c, 0xc6, 0xb8, 0x1b, - 0xff, 0xc9, 0xf8, 0x88, 0x07, 0xb6, 0x45, 0x16, 0xd2, 0xc0, 0x96, 0x10, 0x1f, 0x38, 0x03, 0xe8, - 0x49, 0x22, 0xa1, 0x4a, 0xdf, 0xa3, 0x25, 0x9d, 0xc2, 0x78, 0x2f, 0x1a, 0x13, 0x00, 0x10, 0xff, - 0x0b, 0x4f, 0x0e, 0x82, 0x10, 0xce, 0xf5, 0x57, 0x3d, 0x74, 0x66, 0x62, 0x07, 0xfb, 0x87, 0xf6, - 0x52, 0x3b, 0xa6, 0x2b, 0x6f, 0x8c, 0x44, 0x94, 0x96, 0x13, 0x3c, 0x29, 0x1e, 0x0a, 0x43, 0x78, - 0xc1, 0x65, 0xfd, 0xf0, 0x20, 0x16, 0x76, 0x88, 0xc3, 0x4a, 0x46, 0xf0, 0x1f, 0x28, 0xd2, 0x9c, - 0xd2, 0x61, 0x37, 0x85, 0x6b, 0x64, 0x91, 0x69, 0x54, 0xf0, 0xa5, 0xd2, 0x44, 0x91, 0x90, 0x8b, - 0x21, 0xf4, 0xc5, 0x94, 0x45, 0xf0, 0x49, 0x09, 0xb6, 0xd1, 0x86, 0x01, 0xcb, 0x05, 0x8f, 0x4b, - 0xe4, 0x28, 0x50, 0x10, 0xd9, 0x9f, 0xbc, 0xd6, 0x67, 0xa8, 0xcb, 0x18, 0x1b, 0xc9, 0x42, 0x61, - 0x8b, 0x1c, 0x14, 0x36, 0xca, 0x40, 0x61, 0xb3, 0xfc, 0x53, 0x13, 0x7f, 0x36, 0x9f, 0x62, 0xf9, - 0x02, 0x74, 0x52, 0x47, 0xdd, 0xdb, 0x56, 0x72, 0x28, 0xbc, 0x0b, 0xa0, 0x0b, 0xf8, 0x27, 0x49, - 0x09, 0x25, 0xc6, 0xe8, 0x00, 0xea, 0x85, 0xb7, 0x78, 0xcb, 0x7b, 0x93, 0xa1, 0xdc, 0x1e, 0xd8, - 0xe9, 0x6c, 0x06, 0xa7, 0x6d, 0xf8, 0xc0, 0x79, 0x44, 0x70, 0xb0, 0xcf, 0x33, 0x99, 0xa1, 0xb4, - 0xaa, 0x5e, 0x1a, 0x0e, 0x82, 0x6c, 0xcd, 0x57, 0x69, 0x8c, 0x10, 0x2a, 0xc0, 0x21, 0xf1, 0x3e, - 0x17, 0x29, 0xa2, 0xdb, 0xed, 0xda, 0x7b, 0x8d, 0x54, 0x31, 0xce, 0xed, 0x35, 0x72, 0x26, 0x08, - 0x12, 0x68, 0xfd, 0x53, 0x1c, 0x66, 0x64, 0x87, 0x93, 0xa2, 0x3e, 0x67, 0xa8, 0x66, 0x0a, 0xc6, - 0xf0, 0x52, 0x4f, 0xee, 0xe7, 0xfa, 0xfa, 0xfa, 0x6f, 0x66, 0xad, 0xf2, 0x25, 0x5d, 0x08, 0xde, - 0xcf, 0xc3, 0xcc, 0x8a, 0xe0, 0xf8, 0xbc, 0x2f, 0x47, 0x25, 0xd7, 0x50, 0x7b, 0xd4, 0x78, 0x1e, - 0xa9, 0x75, 0x54, 0xab, 0xa7, 0xb3, 0x0b, 0x7c, 0x36, 0xba, 0x72, 0x0a, 0x8e, 0x91, 0x26, 0xd4, - 0x45, 0x60, 0x8b, 0x1b, 0x99, 0x79, 0x18, 0x71, 0xe0, 0x54, 0x97, 0xd6, 0x34, 0xb0, 0xf7, 0x0f, - 0x0f, 0xc9, 0x30, 0x1d, 0xd8, 0x3d, 0xdb, 0xd2, 0x6f, 0xdd, 0xf4, 0x55, 0x86, 0xa7, 0xc2, 0x8c, - 0xf7, 0xca, 0x71, 0xd5, 0x86, 0x2f, 0xf6, 0xb7, 0x29, 0x15, 0x5b, 0x2b, 0xbe, 0xb4, 0x49, 0x1d, - 0xfc, 0xc7, 0xb9, 0xfa, 0x6c, 0x69, 0x86, 0x6b, 0x58, 0x03, 0x34, 0x5c, 0x9f, 0xa2, 0xf0, 0x63, - 0x7d, 0xfa, 0xf0, 0x03, 0x59, 0xae, 0x57, 0x54, 0x7c, 0x51, 0x7a, 0xe9, 0x71, 0x67, 0xf5, 0x91, - 0xfd, 0x89, 0xcb, 0x26, 0xb0, 0xcd, 0x8b, 0x34, 0xc3, 0x3e, 0xec, 0xe5, 0xba, 0x5e, 0x7e, 0x9e, - 0xcd, 0x60, 0x59, 0x8d, 0x5a, 0x64, 0x2d, 0xdf, 0x70, 0xb4, 0xe2, 0x1d, 0x9a, 0xef, 0xcc, 0x42, - 0x96, 0x64, 0xf2, 0x7d, 0xb9, 0xfa, 0xc9, 0xed, 0xcd, 0x98, 0xcd, 0xc5, 0x3b, 0xf1, 0xc5, 0x5b, - 0x82, 0x97, 0x20, 0x02, 0x26, 0x76, 0x7b, 0x4b, 0xe3, 0xd5, 0x90, 0x24, 0xb8, 0xac, 0xbd, 0x68, - 0x98, 0x67, 0x49, 0x02, 0xd7, 0xec, 0x11, 0xa1, 0x92, 0xe9, 0x59, 0xb4, 0x31, 0x24, 0x65, 0xae, - 0x9b, 0xfa, 0x86, 0x63, 0x67, 0x9b, 0x8e, 0xdd, 0xa9, 0xbe, 0x0f, 0x31, 0xfe, 0x3e, 0x68, 0x0c, - 0x95, 0x59, 0xa5, 0xd9, 0x9a, 0xda, 0xff, 0x9b, 0x13, 0x4e, 0x67, 0xd5, 0x19, 0x6b, 0x63, 0x4f, - 0x67, 0xab, 0x46, 0x37, 0x6c, 0x25, 0x24, 0x29, 0xa0, 0xfd, 0x0b, 0x76, 0xc8, 0x1f, 0x41, 0x9c, - 0xcc, 0x40, 0xcb, 0x5b, 0x37, 0xdd, 0x53, 0xbc, 0xc5, 0x5f, 0x5f, 0xed, 0x1d, 0xba, 0xa2, 0xfc, - 0xbb, 0x37, 0xc0, 0xe5, 0x3c, 0x6b, 0x24, 0xa3, 0x9e, 0xce, 0xa8, 0xb4, 0x65, 0x86, 0xfa, 0xff, - 0xcb, 0x54, 0x90, 0x67, 0x93, 0x95, 0x84, 0x5f, 0x1d, 0x31, 0xd4, 0xff, 0xf7, 0x8d, 0xb8, 0xf1, - 0xfc, 0x9a, 0x24, 0x33, 0x35, 0xe2, 0x49, 0x45, 0x06, 0xe0, 0x47, 0x85, 0x29, 0x03, 0xf8, 0x74, - 0x46, 0x6d, 0x4c, 0xa1, 0xda, 0x29, 0x70, 0x65, 0xac, 0x0c, 0x6c, 0xd6, 0xea, 0x49, 0xd0, 0x20, - 0x27, 0x83, 0xf0, 0x68, 0x48, 0xca, 0xd6, 0x2f, 0x78, 0xf1, 0x90, 0x33, 0x6b, 0x2a, 0x83, 0x59, - 0x2a, 0xe7, 0xe0, 0x0a, 0xb1, 0x19, 0xfa, 0xaa, 0x9c, 0x83, 0xd0, 0x99, 0x2e, 0x36, 0x43, 0x85, - 0x66, 0xb1, 0xf9, 0x19, 0x72, 0x73, 0x45, 0x70, 0xde, 0x6c, 0x52, 0x7f, 0x8e, 0xd0, 0xfc, 0x61, - 0xb1, 0xd9, 0x0c, 0xe6, 0xb4, 0xf9, 0x84, 0x6e, 0xa2, 0xca, 0x7c, 0xfe, 0x1c, 0xe9, 0xd3, 0xb9, - 0x89, 0xfe, 0xf5, 0xb3, 0xd1, 0x5e, 0xe7, 0x94, 0x69, 0x33, 0x68, 0xb1, 0x31, 0x27, 0xe9, 0x2e, - 0xc7, 0xe3, 0x53, 0xe3, 0x9e, 0x73, 0x12, 0x2b, 0xa2, 0x02, 0x45, 0xfe, 0x5f, 0x58, 0xc6, 0xc2, - 0xc2, 0x42, 0x0d, 0x3e, 0xb6, 0xd0, 0x7b, 0xa6, 0xa4, 0xbd, 0x79, 0xa6, 0xd3, 0x5e, 0x95, 0x78, - 0xcb, 0x8e, 0xb9, 0x44, 0xbc, 0x3f, 0x15, 0x64, 0x29, 0xec, 0xd2, 0xcf, 0x1e, 0xd7, 0xd4, 0x18, - 0xd7, 0x0f, 0x64, 0x0f, 0xd6, 0x46, 0x33, 0xd5, 0x75, 0xac, 0x75, 0xa3, 0xf1, 0x0f, 0xbe, 0x6f, - 0x1c, 0x4d, 0xdd, 0x1a, 0x48, 0x9f, 0x35, 0xc3, 0x60, 0x83, 0xf5, 0x1b, 0xb7, 0x65, 0xdd, 0x20, - 0xa8, 0xeb, 0xab, 0xc3, 0x80, 0xc7, 0x43, 0x8e, 0x9c, 0xc6, 0xd6, 0xb7, 0xb0, 0xb6, 0x46, 0xeb, - 0x1d, 0xe3, 0xfd, 0x5f, 0xd9, 0xc3, 0x3f, 0x87, 0x0f, 0xd1, 0xcd, 0xed, 0x8d, 0xc5, 0x39, 0x14, - 0x5e, 0x1f, 0xe4, 0x42, 0x95, 0x07, 0x52, 0x0d, 0x81, 0x40, 0xa6, 0x5d, 0x41, 0x15, 0x3b, 0x7a, - 0xac, 0xaa, 0xef, 0xf6, 0xd5, 0x37, 0xc4, 0x35, 0xd2, 0xfc, 0xe1, 0xf4, 0x88, 0xd6, 0xd2, 0x7b, - 0x29, 0x04, 0x9d, 0x3e, 0x3c, 0x0a, 0x70, 0x25, 0x07, 0x21, 0xaa, 0xf1, 0x25, 0x7b, 0x0b, 0x95, - 0x3f, 0x10, 0x49, 0x9f, 0xf5, 0xbb, 0xf8, 0x52, 0xb6, 0xbd, 0xc0, 0x64, 0x90, 0x64, 0x67, 0x6c, - 0x9c, 0xee, 0x1c, 0x94, 0x32, 0xee, 0x4c, 0xc9, 0x84, 0xeb, 0x8d, 0x90, 0x3b, 0x85, 0x47, 0x8e, - 0xbb, 0x94, 0xc6, 0x83, 0xa3, 0xfd, 0xc6, 0xfb, 0x35, 0x24, 0x06, 0xbb, 0x83, 0x83, 0x74, 0x9b, - 0x3b, 0xf0, 0xb6, 0xf4, 0x69, 0x57, 0x05, 0x10, 0x65, 0x7a, 0x68, 0x99, 0x03, 0x76, 0xbc, 0xcc, - 0xa5, 0xdb, 0x82, 0x0b, 0x82, 0x4a, 0x67, 0xa3, 0x26, 0x4c, 0x35, 0x39, 0x92, 0xb2, 0x52, 0xdb, - 0xf0, 0xe7, 0x2d, 0xc3, 0x17, 0xfa, 0x74, 0x93, 0xdb, 0xde, 0x3c, 0x87, 0x6d, 0x61, 0x57, 0x5b, - 0xc8, 0x95, 0x1a, 0xf6, 0x6a, 0xca, 0x78, 0xcd, 0xfa, 0xf3, 0x81, 0x98, 0x97, 0x66, 0xfc, 0xd9, - 0x31, 0x35, 0x44, 0x34, 0x40, 0x61, 0x0a, 0x88, 0x61, 0xbb, 0xd2, 0xc7, 0x2b, 0x55, 0xb4, 0xb4, - 0x53, 0x51, 0xa8, 0xce, 0x44, 0x5e, 0x4b, 0x2a, 0x28, 0xa4, 0x70, 0x54, 0xf7, 0x23, 0xaf, 0x42, - 0xe7, 0x9b, 0xd8, 0x91, 0x61, 0x8f, 0xef, 0xc8, 0xe6, 0x8a, 0x13, 0x44, 0xd0, 0x89, 0x79, 0x7d, - 0xed, 0x5a, 0x97, 0x4a, 0x4f, 0xf5, 0xc7, 0xf1, 0x33, 0x50, 0xe8, 0x69, 0x7c, 0xdf, 0xee, 0xa0, - 0x77, 0xc1, 0x55, 0x34, 0x43, 0xd3, 0x06, 0x12, 0x3a, 0xf7, 0xa3, 0xf7, 0x07, 0x45, 0x99, 0x63, - 0xb4, 0x00, 0x5a, 0x27, 0xb8, 0xb1, 0x45, 0x34, 0xed, 0xab, 0x97, 0x5d, 0xb8, 0x9d, 0x9e, 0x87, - 0xda, 0x27, 0x41, 0x91, 0x2d, 0xb1, 0x1b, 0xba, 0x52, 0x02, 0x5a, 0x0a, 0xe7, 0xcc, 0xa5, 0x64, - 0xd1, 0xbc, 0x63, 0x6c, 0xe6, 0xf3, 0xaa, 0xf6, 0x29, 0x3a, 0xca, 0xa2, 0x17, 0x1a, 0x01, 0xe8, - 0xe6, 0x05, 0x8c, 0xf4, 0xd1, 0x5e, 0x96, 0x8f, 0x6e, 0x48, 0x6f, 0x5a, 0x74, 0x82, 0x4b, 0x74, - 0x52, 0xc4, 0xad, 0x16, 0xe5, 0x64, 0xee, 0xff, 0xa9, 0xb8, 0x89, 0x31, 0xba, 0xd8, 0xf6, 0x30, - 0xe9, 0x29, 0x42, 0xee, 0xd2, 0x5f, 0x7c, 0x84, 0x87, 0x07, 0xc9, 0xdf, 0xd0, 0x11, 0x8b, 0x97, - 0xd1, 0x93, 0xa0, 0x7f, 0x8a, 0x44, 0x5e, 0x6d, 0x00, 0x3c, 0x11, 0xd1, 0xd1, 0x74, 0x0d, 0xf7, - 0x9f, 0x5c, 0x94, 0x1f, 0x24, 0xf8, 0x6b, 0x6d, 0x59, 0xfe, 0x3f, 0x23, 0xc5, 0x6f, 0xa1, 0x02, - 0x11, 0xd3, 0xde, 0x46, 0x0a, 0x27, 0xf2, 0x67, 0xc3, 0xf8, 0x19, 0x43, 0x0f, 0x19, 0x09, 0x54, - 0xc2, 0x78, 0xf7, 0x62, 0x01, 0xbd, 0x43, 0xdd, 0x13, 0x28, 0x3c, 0xce, 0x81, 0x04, 0x78, 0x67, - 0xae, 0xba, 0x04, 0xbb, 0x30, 0xc8, 0x49, 0x44, 0xad, 0x37, 0xd3, 0xd3, 0x9c, 0x4d, 0xe7, 0xe1, - 0xbd, 0xe8, 0xcc, 0x91, 0xb7, 0x4a, 0x0d, 0x8e, 0xc0, 0xf6, 0x77, 0xa2, 0x1f, 0xab, 0x8b, 0x03, - 0xb2, 0x57, 0x12, 0xdb, 0x82, 0x2f, 0x0c, 0xd2, 0x55, 0x17, 0x1d, 0x54, 0x60, 0x6e, 0x84, 0x35, - 0xcd, 0x4b, 0x4c, 0x14, 0x2b, 0xbb, 0xab, 0x26, 0xe8, 0x0e, 0x90, 0xcb, 0xe7, 0x9c, 0x33, 0x06, - 0xe6, 0x24, 0x11, 0x2a, 0x43, 0xf7, 0x87, 0x33, 0x16, 0x80, 0xee, 0x7a, 0x75, 0xc0, 0x80, 0xd2, - 0x7d, 0xb1, 0x2c, 0xc3, 0x48, 0x5d, 0x02, 0x69, 0x91, 0x4e, 0x58, 0xd2, 0x08, 0x6a, 0x11, 0x9b, - 0x1d, 0xf4, 0x28, 0xd9, 0x94, 0x00, 0xaf, 0x22, 0x80, 0x8f, 0xa0, 0xc0, 0x8b, 0xa0, 0x4a, 0x42, - 0x02, 0xbc, 0x16, 0x12, 0xbf, 0xe3, 0x25, 0x1f, 0x25, 0x5a, 0x14, 0x4e, 0xaf, 0xe2, 0x3e, 0x9a, - 0xaf, 0xa1, 0x3f, 0x60, 0x25, 0xde, 0x23, 0xc3, 0x45, 0xe1, 0x68, 0x08, 0x79, 0x80, 0xbe, 0xc1, - 0x74, 0xac, 0xc6, 0x5e, 0xe8, 0xa5, 0x5e, 0x04, 0x07, 0x37, 0x0e, 0xcc, 0x7c, 0x4e, 0xee, 0xba, - 0x51, 0x80, 0x80, 0x30, 0x98, 0x14, 0xef, 0x25, 0xde, 0xe3, 0xc5, 0x01, 0x62, 0x66, 0x79, 0x21, - 0xfc, 0xd9, 0xc7, 0x30, 0x9d, 0xfc, 0xec, 0xe0, 0x7c, 0x40, 0xf7, 0x7a, 0xd0, 0xd8, 0x9e, 0xdb, - 0x41, 0x90, 0xbb, 0x31, 0x8e, 0xe7, 0xa5, 0x91, 0x25, 0x0c, 0x1a, 0xd4, 0xcb, 0xd2, 0x7a, 0x99, - 0xea, 0x4a, 0xe4, 0xa7, 0x50, 0x17, 0x8c, 0xbd, 0xf3, 0xdd, 0xde, 0x20, 0x0e, 0x04, 0xe6, 0x30, - 0xa3, 0x20, 0xa1, 0x50, 0xfb, 0xda, 0xc3, 0xc1, 0x94, 0x5f, 0xf7, 0xcf, 0x97, 0x5b, 0x08, 0x6a, - 0x8b, 0xd1, 0x3e, 0x51, 0xc0, 0xf6, 0xe4, 0x1a, 0xbc, 0x24, 0x74, 0x17, 0x0e, 0x9f, 0xe8, 0x5c, - 0x60, 0xa0, 0x37, 0x9c, 0xb6, 0x4b, 0x0f, 0x25, 0x0d, 0x0f, 0x6f, 0xc0, 0x5c, 0xbc, 0x51, 0x59, - 0xfe, 0xe1, 0x42, 0x22, 0xa2, 0x5c, 0x68, 0x18, 0xaf, 0x56, 0x35, 0x92, 0x59, 0xe2, 0xb8, 0x7a, - 0x68, 0xa3, 0xfe, 0x9f, 0x34, 0x4a, 0xd0, 0xe9, 0x64, 0x70, 0x61, 0x52, 0x48, 0x7d, 0x03, 0x92, - 0x97, 0xa6, 0x87, 0xe0, 0xcb, 0xc0, 0x72, 0x43, 0xc3, 0x5d, 0xb3, 0xb2, 0xe1, 0x48, 0x74, 0x9d, - 0x3d, 0xe0, 0xbf, 0x5c, 0x5a, 0x45, 0x32, 0xde, 0x45, 0x01, 0x16, 0xfd, 0x34, 0x91, 0x5d, 0x1c, - 0x73, 0x76, 0xf1, 0x62, 0x91, 0x2f, 0x1d, 0x2c, 0x73, 0xb9, 0x84, 0x6e, 0x2a, 0x21, 0x38, 0xde, - 0x14, 0xfa, 0xd1, 0x74, 0x10, 0x83, 0x11, 0xd2, 0xef, 0x9a, 0x56, 0x8a, 0x5d, 0x23, 0x3b, 0xc0, - 0xcf, 0x4c, 0x1a, 0x6f, 0x0d, 0x19, 0x92, 0x5a, 0xa0, 0x0e, 0x61, 0xa8, 0x0f, 0x9a, 0xee, 0x60, - 0xd4, 0xc6, 0xa9, 0x94, 0x76, 0x78, 0x74, 0x2f, 0xc5, 0x1a, 0xaa, 0x1d, 0xbe, 0x01, 0x2a, 0x20, - 0xae, 0x51, 0x2e, 0xde, 0x18, 0x47, 0x47, 0x3e, 0x3b, 0x3c, 0xd7, 0x97, 0x48, 0xf3, 0x65, 0x1d, - 0xec, 0xf7, 0xc5, 0xde, 0xf9, 0x70, 0x7a, 0x72, 0xb0, 0x6f, 0x53, 0x42, 0x2d, 0xeb, 0x8d, 0x5e, - 0xfa, 0x66, 0xff, 0xf5, 0x6b, 0x5b, 0xbc, 0x42, 0x7b, 0xa4, 0x09, 0x2f, 0xe3, 0xc4, 0x70, 0x3e, - 0xb4, 0x31, 0xb8, 0x07, 0xc3, 0xd8, 0x88, 0xfd, 0x8d, 0x58, 0x37, 0xca, 0xfa, 0xfc, 0x73, 0xfd, - 0xb0, 0x23, 0x10, 0x16, 0x1d, 0x31, 0x14, 0xde, 0x5e, 0x8e, 0xff, 0x60, 0xb8, 0x1b, 0xe6, 0xd8, - 0xee, 0x26, 0x84, 0x91, 0xb6, 0x10, 0x1f, 0xf8, 0x41, 0x46, 0x0d, 0x30, 0xaa, 0x86, 0xcc, 0xce, - 0x74, 0x13, 0xcf, 0x8e, 0xf9, 0xe1, 0x86, 0x2d, 0x42, 0xbc, 0xbb, 0xac, 0xc8, 0xcb, 0xb2, 0xbd, - 0xd8, 0xb5, 0xa1, 0x0c, 0x9e, 0x43, 0xc0, 0x7b, 0xfa, 0xe5, 0x2c, 0x24, 0x1e, 0x92, 0xf2, 0xe6, - 0x79, 0x79, 0x39, 0x7c, 0xd1, 0xe0, 0xbb, 0xa6, 0x54, 0x15, 0x0b, 0xf4, 0x74, 0xed, 0x84, 0x51, - 0xf1, 0x53, 0xc2, 0xed, 0x73, 0x47, 0xf9, 0x6c, 0xa5, 0xb0, 0x0a, 0xcb, 0x1d, 0xee, 0xfc, 0xc3, - 0xd7, 0x2b, 0x75, 0x75, 0x07, 0xa0, 0x0b, 0x28, 0xa6, 0x57, 0x93, 0xf2, 0xe4, 0x6b, 0x4b, 0xba, - 0x31, 0x8e, 0xa0, 0xb6, 0x1f, 0x04, 0x29, 0x5e, 0x03, 0x8f, 0xec, 0xcf, 0x7b, 0xef, 0xec, 0x3e, - 0x7d, 0x5e, 0xe2, 0x71, 0x75, 0xe1, 0x7a, 0x71, 0xa7, 0xb3, 0x5c, 0xc2, 0x32, 0x4c, 0x27, 0x47, - 0xfe, 0x28, 0xe9, 0x04, 0xf6, 0x47, 0x01, 0x64, 0x68, 0xe1, 0xe6, 0x40, 0x67, 0x3e, 0x09, 0x10, - 0xd4, 0xb5, 0xfb, 0xd0, 0x51, 0xcc, 0xa1, 0x96, 0x30, 0x80, 0x28, 0x45, 0xaf, 0x43, 0x05, 0x7c, - 0x98, 0x5b, 0x33, 0xdc, 0x8e, 0x5d, 0x8c, 0x52, 0xc4, 0x1b, 0xba, 0x16, 0x93, 0x18, 0x6e, 0x09, - 0xfa, 0xa3, 0x2e, 0x48, 0x8f, 0xf9, 0x25, 0xaf, 0xea, 0xa8, 0x8f, 0xd7, 0xa4, 0xb4, 0x84, 0x9c, - 0xfc, 0x56, 0x5c, 0x85, 0x26, 0x86, 0x3e, 0x95, 0x98, 0x3e, 0x78, 0x9c, 0x30, 0x94, 0x5f, 0xd7, - 0x1e, 0x3a, 0x82, 0xed, 0xd1, 0x8b, 0x92, 0x1e, 0x71, 0xac, 0xee, 0x11, 0x57, 0x56, 0x52, 0xfe, - 0x70, 0x6c, 0x13, 0x7f, 0x38, 0xe5, 0x96, 0x7a, 0x1b, 0x4f, 0x2d, 0x44, 0x79, 0xc4, 0x11, 0x58, - 0x38, 0x04, 0x0b, 0xcf, 0x45, 0xee, 0xc7, 0xdf, 0xec, 0x28, 0x67, 0xd0, 0xb1, 0xbc, 0xa0, 0xf5, - 0xd8, 0x37, 0x39, 0xca, 0x2d, 0xdd, 0x7a, 0xa4, 0x0b, 0x29, 0x72, 0xfc, 0x14, 0x14, 0xc8, 0xa3, - 0xf2, 0x00, 0xc4, 0x44, 0x83, 0x79, 0x15, 0x19, 0x0c, 0x76, 0x5b, 0x28, 0x8b, 0x10, 0xc8, 0xfd, - 0x81, 0x6b, 0x3b, 0x7d, 0x38, 0x13, 0x3c, 0x42, 0xe1, 0x7a, 0xc9, 0x78, 0xc9, 0x5e, 0x31, 0x70, - 0x92, 0xf2, 0x86, 0x17, 0xce, 0xe0, 0x23, 0xf2, 0x23, 0x47, 0x20, 0x2e, 0xe9, 0xe4, 0x51, 0x43, - 0x99, 0x78, 0xcb, 0x41, 0x26, 0x78, 0xd0, 0xec, 0xf8, 0xd2, 0x25, 0xb6, 0xf3, 0x07, 0x59, 0x30, - 0xd9, 0x7d, 0x25, 0x4a, 0xdc, 0x0b, 0x10, 0x39, 0x39, 0x04, 0x3e, 0x2e, 0x4c, 0x6b, 0x6c, 0x8e, - 0x79, 0xa3, 0x81, 0xd1, 0x93, 0x75, 0xe4, 0x89, 0x7c, 0x59, 0x5d, 0x13, 0x1e, 0x14, 0xe4, 0x68, - 0x0b, 0x21, 0x50, 0xae, 0xda, 0x9e, 0xa8, 0x82, 0x34, 0xd3, 0xdb, 0x82, 0x20, 0xa8, 0xd0, 0x39, - 0xad, 0x20, 0x64, 0x0d, 0xcd, 0x1f, 0xce, 0xc0, 0xca, 0xd2, 0xb1, 0x08, 0xb4, 0x48, 0x24, 0x2e, - 0x93, 0xd7, 0x1f, 0x57, 0x91, 0x7b, 0xe4, 0x73, 0x1a, 0xc2, 0x7b, 0x38, 0xa8, 0x8b, 0x5d, 0x7b, - 0xd1, 0xa8, 0xbe, 0xf3, 0xd7, 0xdc, 0xa2, 0xbe, 0x32, 0x54, 0x77, 0x6b, 0xb1, 0x1b, 0xab, 0x2a, - 0x8b, 0x99, 0x78, 0xc9, 0x06, 0xd5, 0x4b, 0xbd, 0xb8, 0x7e, 0x19, 0xe0, 0xf7, 0x0b, 0x8c, 0x05, - 0xb1, 0x85, 0x8f, 0xba, 0x4b, 0x3e, 0x38, 0x09, 0x68, 0xb3, 0x79, 0x80, 0xa1, 0x1d, 0xc0, 0xec, - 0x73, 0x1b, 0x13, 0xb4, 0x24, 0x14, 0xa5, 0x6b, 0xf7, 0xe8, 0xb2, 0xc7, 0xf5, 0x38, 0xb9, 0xb6, - 0x3e, 0xf1, 0x72, 0x9e, 0xc9, 0xf9, 0xc4, 0x1b, 0x0c, 0x90, 0xaa, 0xcb, 0x19, 0x85, 0x9b, 0x2c, - 0x40, 0x36, 0x51, 0x0d, 0x06, 0x82, 0xf5, 0xc5, 0x41, 0x4f, 0xfa, 0xf9, 0x90, 0x80, 0x34, 0x61, - 0xb0, 0xb9, 0x92, 0x3d, 0x27, 0xee, 0x84, 0x70, 0x60, 0x6c, 0xf1, 0x68, 0xe3, 0x14, 0x65, 0xc0, - 0x78, 0xd8, 0x7b, 0x7a, 0x0a, 0x87, 0xbe, 0xcb, 0x7d, 0xb7, 0x2f, 0x2c, 0x07, 0x05, 0x19, 0xeb, - 0x2e, 0x9a, 0x17, 0xb7, 0x61, 0xec, 0x82, 0x28, 0xd3, 0xfa, 0x60, 0x58, 0x32, 0x23, 0xd2, 0xa1, - 0x4e, 0xc0, 0xe8, 0x96, 0xd5, 0xe0, 0x6b, 0x6f, 0xee, 0x0d, 0x72, 0x3b, 0x74, 0xb5, 0x18, 0x10, - 0x04, 0x4c, 0x77, 0x0b, 0x8d, 0xa4, 0x38, 0x74, 0xa2, 0x53, 0xba, 0x4b, 0x54, 0x6b, 0xaf, 0xea, - 0x1d, 0xdd, 0x56, 0xdd, 0x75, 0xdd, 0xa1, 0xc8, 0xa8, 0xe3, 0x8c, 0xa1, 0xf3, 0x5c, 0x4b, 0x10, - 0x18, 0x86, 0x79, 0x18, 0x75, 0x97, 0xe9, 0x85, 0x3b, 0x48, 0x10, 0xab, 0x4e, 0x5e, 0x67, 0x1b, - 0x3f, 0x0b, 0x77, 0xb6, 0x44, 0x1b, 0x40, 0x38, 0x9d, 0xea, 0x4f, 0xf7, 0x9c, 0x8d, 0x1e, 0xf3, - 0x35, 0x1e, 0x5f, 0x20, 0x86, 0xf0, 0x8a, 0x9e, 0x6a, 0x2f, 0x00, 0x01, 0x62, 0x1a, 0x62, 0xaf, - 0xf8, 0x61, 0x7c, 0x82, 0x97, 0xd0, 0x46, 0x04, 0x16, 0x82, 0xc9, 0xd1, 0x85, 0x34, 0x46, 0xf9, - 0x61, 0x04, 0x52, 0x5b, 0xd3, 0xcf, 0xb1, 0xd1, 0x2e, 0x89, 0xdf, 0x6d, 0xd4, 0xec, 0xf4, 0x31, - 0x99, 0x18, 0x0d, 0x25, 0x22, 0x9d, 0xd1, 0x7a, 0xa5, 0x65, 0x4f, 0xb0, 0xd2, 0xf7, 0xf3, 0x08, - 0xde, 0xea, 0x46, 0x55, 0x4f, 0x33, 0x86, 0x6b, 0xb3, 0x59, 0x65, 0xd8, 0x69, 0x2c, 0xc9, 0xa3, - 0xe2, 0x11, 0x1b, 0xd4, 0x2b, 0xdd, 0xe3, 0x9d, 0x7a, 0xcd, 0xee, 0x25, 0x61, 0x85, 0x2a, 0xe8, - 0x37, 0x4d, 0xed, 0xc7, 0x61, 0xdc, 0xd4, 0xfc, 0xf8, 0xf8, 0x4b, 0xa5, 0xb1, 0x06, 0x0e, 0xa4, - 0xa1, 0xd3, 0x0c, 0xea, 0x5d, 0x5e, 0xb1, 0x87, 0xfb, 0x5a, 0x97, 0x98, 0xb8, 0xe5, 0x61, 0xfd, - 0x70, 0xb2, 0xb6, 0xe9, 0x10, 0x14, 0x14, 0x9c, 0x6c, 0x84, 0x1d, 0xb6, 0xbe, 0x9f, 0xeb, 0x95, - 0xfd, 0x6c, 0xf1, 0x09, 0x3e, 0xa7, 0x43, 0x50, 0xad, 0xd6, 0x0c, 0x0d, 0x6a, 0xac, 0xef, 0xe6, - 0xeb, 0x24, 0xdf, 0xbd, 0x5f, 0xd1, 0x09, 0x61, 0xd3, 0x6d, 0xb0, 0x4c, 0x61, 0xbc, 0x62, 0x34, - 0xd5, 0xf6, 0x9a, 0x47, 0xaa, 0x19, 0x9f, 0xad, 0x7b, 0xa5, 0x8a, 0x2e, 0x7e, 0xc1, 0x10, 0x15, - 0x09, 0x5f, 0x56, 0x5b, 0x04, 0xfc, 0x51, 0x3d, 0x94, 0xd2, 0x69, 0x01, 0xdb, 0xbb, 0x41, 0x97, - 0x54, 0xf2, 0x68, 0xf2, 0x49, 0xd9, 0x4f, 0xe7, 0x38, 0x13, 0xe1, 0xdb, 0x04, 0xc2, 0xb1, 0x4d, - 0x15, 0x51, 0x54, 0xfb, 0xbb, 0xe3, 0x67, 0x0f, 0x18, 0xef, 0xa2, 0x95, 0x60, 0x4e, 0x12, 0xd7, - 0x16, 0xba, 0x3e, 0x36, 0xcb, 0x46, 0xf4, 0x2f, 0x06, 0x27, 0x26, 0x32, 0x96, 0x60, 0x9f, 0x90, - 0x4c, 0x6d, 0x0b, 0x11, 0x9c, 0x2d, 0xbb, 0x93, 0x34, 0xed, 0x84, 0xd8, 0xf4, 0xef, 0xbb, 0x20, - 0x9c, 0x83, 0x08, 0x6f, 0x00, 0xe0, 0xac, 0xa1, 0xc8, 0x1c, 0x1c, 0x12, 0x3a, 0xf9, 0x74, 0xe3, - 0x9b, 0x25, 0xa9, 0x9e, 0x28, 0x31, 0x35, 0xc9, 0xc7, 0x34, 0x4f, 0xbc, 0x86, 0x6f, 0x20, 0x63, - 0x3e, 0x53, 0x9a, 0x66, 0x65, 0x9d, 0xb5, 0xa0, 0x2e, 0xa1, 0x10, 0xe9, 0x18, 0x6e, 0x5c, 0x34, - 0x3f, 0xe2, 0x3a, 0xd2, 0x68, 0xb7, 0xd7, 0xef, 0x69, 0xf5, 0x6f, 0xb2, 0x93, 0x12, 0x14, 0x1d, - 0x33, 0xcb, 0x77, 0x13, 0x19, 0xe2, 0xc2, 0x40, 0x62, 0x1a, 0xa2, 0xc9, 0x44, 0xe0, 0x3e, 0xa2, - 0x01, 0x08, 0x7f, 0x47, 0x90, 0xdb, 0xf9, 0x31, 0x62, 0x45, 0x52, 0x78, 0x3f, 0x1a, 0x40, 0xea, - 0xc5, 0x83, 0x87, 0x20, 0x19, 0xbe, 0xfa, 0x1e, 0x58, 0xf4, 0xd1, 0xe1, 0x9b, 0xa7, 0xa7, 0x64, - 0xf8, 0xfa, 0x15, 0x7e, 0x7e, 0xdb, 0xc3, 0xcf, 0x6f, 0x5f, 0xe3, 0xe7, 0xde, 0xfe, 0x01, 0x7e, - 0x01, 0x29, 0x75, 0x64, 0x07, 0x30, 0x95, 0xa1, 0xed, 0x3d, 0x06, 0x39, 0x35, 0xca, 0xa9, 0x51, - 0x4e, 0x8d, 0x72, 0x6a, 0x94, 0x53, 0xa3, 0x9c, 0x37, 0xca, 0xf5, 0x46, 0x32, 0xb1, 0x94, 0x43, - 0xa3, 0x93, 0xd1, 0x7b, 0x23, 0xfb, 0xc8, 0xee, 0x3f, 0xb8, 0x1d, 0x31, 0xa5, 0x8a, 0xbe, 0x48, - 0xe6, 0x1e, 0xb3, 0xee, 0xa3, 0xdb, 0xe1, 0xf3, 0xf0, 0x44, 0x3e, 0xb3, 0x05, 0x25, 0x8a, 0x88, - 0x26, 0x84, 0x81, 0xaa, 0x5b, 0xd8, 0xb4, 0x90, 0xe0, 0xc5, 0x7d, 0xbe, 0xbd, 0x7d, 0x9f, 0x03, - 0x17, 0x4f, 0x73, 0x0c, 0xea, 0xbf, 0xe7, 0xc9, 0xcc, 0x15, 0x42, 0xd9, 0xf6, 0xb6, 0xc2, 0x2a, - 0xbb, 0xcf, 0x87, 0xbb, 0x3d, 0x23, 0xe6, 0xe5, 0x26, 0xbc, 0x66, 0xbf, 0x9d, 0x7a, 0x87, 0x46, - 0x08, 0x32, 0x2f, 0xa4, 0x9e, 0x9f, 0x9e, 0x1c, 0xe7, 0x9e, 0x83, 0xc1, 0xfc, 0xc6, 0xc6, 0xa7, - 0xf0, 0xbe, 0x19, 0x68, 0xc1, 0x02, 0x0b, 0x71, 0x05, 0x7c, 0xc7, 0xc8, 0xbe, 0xcf, 0x41, 0x38, - 0x83, 0x7f, 0x31, 0xda, 0x96, 0xe0, 0x2b, 0x08, 0xcd, 0x82, 0x63, 0x58, 0x54, 0x5b, 0x5d, 0xa5, - 0x79, 0x41, 0xaa, 0x5b, 0xc7, 0xde, 0xc3, 0x16, 0x2e, 0xe6, 0x6b, 0x0b, 0xe7, 0x8f, 0x5f, 0xc8, - 0xa0, 0x41, 0x88, 0x13, 0xe3, 0xdb, 0xd9, 0x0c, 0x61, 0x13, 0xef, 0x31, 0x67, 0xf0, 0x0d, 0xcb, - 0xf3, 0x50, 0x4f, 0x43, 0xc8, 0x24, 0xa0, 0x3e, 0x99, 0x06, 0x75, 0x54, 0x7d, 0xb2, 0x7c, 0xbd, - 0xa7, 0xc6, 0x4a, 0x88, 0xd5, 0x41, 0x39, 0xa8, 0x81, 0x3b, 0x30, 0xc2, 0xc2, 0xb5, 0xd8, 0x1f, - 0xd7, 0xd3, 0xbe, 0xf0, 0x75, 0xad, 0x05, 0x53, 0x7b, 0xdf, 0x12, 0xd8, 0xee, 0x7f, 0xff, 0x56, - 0x6e, 0x7b, 0x54, 0x06, 0x66, 0xe9, 0xa0, 0xf5, 0x6c, 0x27, 0xdd, 0xb3, 0xba, 0x15, 0x13, 0xd4, - 0xbd, 0xd1, 0x83, 0x5d, 0xac, 0x25, 0x6d, 0x7d, 0xc2, 0x4c, 0x1e, 0x7e, 0xff, 0xbd, 0x5f, 0xe5, - 0x81, 0x0a, 0xad, 0x34, 0x11, 0x48, 0xb4, 0xdb, 0xdb, 0x86, 0x1f, 0x7e, 0x42, 0x5b, 0x88, 0xa7, - 0xe0, 0xf5, 0x0c, 0x5e, 0x98, 0x78, 0x39, 0x62, 0x65, 0x84, 0x53, 0x91, 0x13, 0x93, 0xd7, 0x41, - 0xe4, 0x60, 0x7a, 0x13, 0x44, 0x74, 0x01, 0x2a, 0xa4, 0x82, 0x0a, 0xb1, 0xea, 0x23, 0xd5, 0x0d, - 0x82, 0x40, 0x91, 0x4c, 0xf7, 0xe7, 0x93, 0x0f, 0x9f, 0x81, 0x96, 0xbe, 0x15, 0x02, 0x40, 0x12, - 0xb5, 0x2b, 0x1e, 0x8b, 0x30, 0x07, 0xf4, 0xd4, 0x39, 0xfb, 0x9a, 0x7f, 0x62, 0x97, 0x61, 0x1c, - 0x54, 0x77, 0x88, 0x1c, 0xb1, 0x44, 0x20, 0x20, 0x60, 0x5e, 0xcc, 0x63, 0xe9, 0xad, 0x94, 0x61, - 0x44, 0x64, 0x1e, 0x43, 0x87, 0x38, 0x02, 0xf1, 0x05, 0xce, 0x45, 0x4e, 0xc7, 0x1c, 0x26, 0x97, - 0xbe, 0x4d, 0x6f, 0xe7, 0x02, 0x2c, 0x97, 0xbe, 0x16, 0xbc, 0x2a, 0x61, 0xd8, 0x52, 0xc1, 0x0d, - 0x7c, 0x2a, 0xc1, 0x7d, 0x59, 0xf7, 0x76, 0x9a, 0x25, 0xa0, 0xd3, 0x24, 0xd3, 0x0a, 0x78, 0x2e, - 0x9c, 0xbf, 0x39, 0xe2, 0xdb, 0x8a, 0xc3, 0x84, 0xf2, 0x5f, 0x35, 0x8c, 0xae, 0x28, 0xd4, 0xb0, - 0x8a, 0xf9, 0x5e, 0xcf, 0xf7, 0x1a, 0x3c, 0xb6, 0x05, 0x29, 0xf9, 0x1e, 0x3a, 0x5e, 0x0b, 0xc0, - 0x60, 0x85, 0x21, 0xac, 0x60, 0x83, 0x2b, 0xf6, 0xf5, 0x06, 0xf7, 0x6c, 0xb4, 0xb4, 0xf3, 0x7c, - 0x65, 0x39, 0x1e, 0x63, 0xe8, 0x95, 0x5d, 0xe0, 0x45, 0x80, 0xe6, 0x26, 0x4d, 0x87, 0x11, 0xa6, - 0x68, 0xec, 0xf1, 0x70, 0x5c, 0xaa, 0x01, 0x32, 0xaf, 0xbb, 0xc8, 0x8f, 0xf8, 0x49, 0x45, 0x5a, - 0xdb, 0xbe, 0x70, 0xed, 0x36, 0x80, 0x12, 0x49, 0xb3, 0x9b, 0x9c, 0x69, 0x1d, 0x4b, 0xa4, 0xcb, - 0xa7, 0xa0, 0xb7, 0x1d, 0x2b, 0x04, 0xca, 0xa7, 0x60, 0x9f, 0x7f, 0x83, 0x71, 0x3f, 0x05, 0xaf, - 0xb6, 0xe3, 0x25, 0x37, 0xa8, 0xf1, 0x86, 0x09, 0x19, 0xcc, 0x30, 0x4a, 0xdf, 0x5d, 0x34, 0x3f, - 0x20, 0xd4, 0x7a, 0x36, 0x3a, 0xd6, 0xfa, 0xa5, 0x6e, 0x91, 0x6b, 0x84, 0xf2, 0xb0, 0xd1, 0x2c, - 0x32, 0x9f, 0x53, 0x4b, 0xae, 0xae, 0x30, 0xc2, 0x10, 0x47, 0xd6, 0x60, 0x1e, 0x05, 0xe4, 0x72, - 0x88, 0x92, 0x60, 0xc5, 0x44, 0x7f, 0x40, 0xb6, 0x79, 0xfa, 0x05, 0x63, 0x9e, 0xc5, 0x29, 0x7e, - 0x00, 0x6b, 0xa3, 0xf0, 0x3d, 0x4b, 0x3d, 0x52, 0x56, 0x43, 0x5c, 0x2b, 0xc2, 0x82, 0xe1, 0xa0, - 0xa1, 0x03, 0x0d, 0xbf, 0x06, 0x91, 0x97, 0xa5, 0x5a, 0x0b, 0xd5, 0x27, 0xc5, 0xf6, 0x36, 0xfd, - 0x19, 0x06, 0x7e, 0xf3, 0x1d, 0x32, 0x91, 0xf3, 0x3b, 0x45, 0x35, 0x54, 0xd9, 0xf5, 0xd6, 0x08, - 0xe4, 0xaa, 0x72, 0xfe, 0xe0, 0x6d, 0x20, 0x8e, 0xab, 0xea, 0xd1, 0x83, 0xd0, 0x6c, 0xc5, 0x1d, - 0xa3, 0x69, 0x92, 0xb8, 0xe0, 0x60, 0x2f, 0xdc, 0x00, 0x3d, 0x7b, 0xb0, 0xcf, 0xcf, 0x94, 0x61, - 0x3a, 0xec, 0xce, 0x1e, 0x96, 0xf6, 0x39, 0x68, 0x6b, 0xe9, 0x28, 0xed, 0x0a, 0x57, 0x17, 0xd8, - 0xc7, 0x7d, 0x75, 0x2e, 0xcc, 0x59, 0x4c, 0x70, 0x51, 0x22, 0x2b, 0x70, 0xe3, 0x03, 0xec, 0xae, - 0xae, 0x37, 0x62, 0x62, 0xd3, 0x95, 0x9a, 0x25, 0x4f, 0x9f, 0xd4, 0x3c, 0x54, 0xba, 0x35, 0x3a, - 0xd3, 0xac, 0xf2, 0xe5, 0x08, 0x81, 0x4e, 0xe6, 0x2b, 0x34, 0x46, 0x0d, 0x28, 0x1b, 0xdb, 0x48, - 0xbc, 0xe7, 0x55, 0x6b, 0x21, 0xef, 0xed, 0x8c, 0x05, 0x81, 0x42, 0x7a, 0x9e, 0xb6, 0x1a, 0x9e, - 0x13, 0x05, 0xcd, 0xdd, 0xe9, 0x33, 0x77, 0xd7, 0x4d, 0xbc, 0x6d, 0x48, 0x0d, 0x73, 0x2e, 0x07, - 0xd1, 0x3a, 0xdf, 0xe2, 0xe9, 0x69, 0xde, 0xcd, 0x27, 0xf3, 0x34, 0x8e, 0x81, 0x24, 0xd2, 0xbf, - 0x46, 0xec, 0xde, 0x59, 0x8c, 0xd9, 0x55, 0x78, 0x17, 0x61, 0x1a, 0x53, 0x09, 0x25, 0x43, 0x87, - 0x10, 0x9c, 0x41, 0x70, 0x60, 0xb2, 0x1c, 0x6d, 0xa0, 0xc0, 0x30, 0x08, 0x28, 0x84, 0xc2, 0xdf, - 0xc4, 0x67, 0x7e, 0x2a, 0x4f, 0x30, 0xda, 0x44, 0x5e, 0x0d, 0xc8, 0x1f, 0xe8, 0x2a, 0xa0, 0xe7, - 0xf7, 0xe1, 0xc7, 0xd2, 0x44, 0x4a, 0x39, 0x8a, 0xad, 0x99, 0x0a, 0xbc, 0xd9, 0xb2, 0x07, 0x63, - 0x60, 0xf7, 0xd7, 0xfc, 0xe2, 0xa0, 0xd7, 0xc3, 0xda, 0x18, 0x17, 0xc6, 0x92, 0xf4, 0xf6, 0xf2, - 0xca, 0xca, 0xb3, 0x70, 0xc2, 0x30, 0xaa, 0x3b, 0xc7, 0xa8, 0x7a, 0x1e, 0xae, 0x56, 0x69, 0xb2, - 0x8f, 0x4d, 0x04, 0xb6, 0x03, 0x3e, 0x41, 0x58, 0xa4, 0x8d, 0x3a, 0x6f, 0xb1, 0xce, 0x3b, 0xed, - 0xb1, 0x16, 0x8d, 0xd1, 0x02, 0x5e, 0x62, 0xa5, 0x13, 0xe0, 0xeb, 0x68, 0xe9, 0x5e, 0x6a, 0x1c, - 0x84, 0x24, 0x04, 0x0a, 0xd8, 0xa1, 0x8a, 0x20, 0x04, 0xc1, 0x97, 0x09, 0x45, 0xce, 0x95, 0x5c, - 0x84, 0xd8, 0x9a, 0x76, 0x86, 0xf5, 0x06, 0xda, 0x09, 0x56, 0xc2, 0x3f, 0xd1, 0x19, 0xe6, 0x37, - 0x1b, 0xe9, 0x36, 0x38, 0x41, 0xc3, 0xb7, 0x3e, 0xf0, 0x30, 0x06, 0xef, 0x4c, 0x3e, 0x09, 0xf8, - 0x66, 0x89, 0xda, 0xae, 0x90, 0xfe, 0x34, 0x51, 0x07, 0xce, 0x6a, 0x5d, 0xf0, 0xd1, 0x24, 0x46, - 0x33, 0x8c, 0xdb, 0x3b, 0x60, 0x07, 0xea, 0xae, 0x92, 0x27, 0x85, 0x45, 0xcc, 0x1d, 0x6e, 0x10, - 0xcf, 0x23, 0xbb, 0xcf, 0xd4, 0x17, 0x3c, 0x8d, 0x41, 0x20, 0xd9, 0xd3, 0x83, 0xcb, 0xf3, 0x9a, - 0x29, 0x1d, 0x0d, 0xfd, 0x17, 0xae, 0x38, 0x3b, 0x30, 0x6a, 0x1d, 0x13, 0x10, 0xad, 0x97, 0x2d, - 0xbc, 0x10, 0x81, 0x0d, 0x32, 0x90, 0x23, 0x6d, 0x6e, 0x6f, 0xa7, 0x70, 0x3d, 0xb4, 0x99, 0x12, - 0x9c, 0x3e, 0x9c, 0xb0, 0x30, 0x7c, 0x1d, 0x36, 0x06, 0x27, 0xdc, 0x4d, 0xd2, 0x7b, 0xc7, 0xc5, - 0x58, 0x43, 0x69, 0x9c, 0x6b, 0x3e, 0x7c, 0xf9, 0xf5, 0x0c, 0x6c, 0xd3, 0x68, 0x0a, 0xac, 0x8f, - 0x7f, 0x90, 0xb9, 0x88, 0x78, 0xbe, 0x01, 0x75, 0x7c, 0xcb, 0xfc, 0x6b, 0x8a, 0xbd, 0xf7, 0xfc, - 0x97, 0xa9, 0x34, 0x06, 0x46, 0x5b, 0x70, 0x80, 0x93, 0x67, 0x53, 0xd9, 0x20, 0x88, 0xdc, 0xa5, - 0x23, 0x82, 0x9d, 0xcb, 0xb8, 0x4d, 0xa6, 0x01, 0x49, 0x20, 0xe0, 0xb6, 0x13, 0x53, 0x06, 0xf6, - 0x78, 0x74, 0x9f, 0x93, 0x20, 0xe1, 0x24, 0x4f, 0x4f, 0x3b, 0x0b, 0xfb, 0xce, 0xee, 0x23, 0x34, - 0xd7, 0x72, 0xc7, 0xed, 0xf3, 0x4b, 0x87, 0x5c, 0x5d, 0x3a, 0x84, 0x1e, 0x02, 0x03, 0x32, 0x84, - 0x80, 0x47, 0xa0, 0x20, 0x60, 0xdb, 0xc5, 0x2e, 0xde, 0xef, 0xc0, 0xf2, 0x20, 0xec, 0x5c, 0xc4, - 0x39, 0x2c, 0xbd, 0x8c, 0x81, 0x85, 0x5a, 0x13, 0x0a, 0x24, 0xbf, 0x7e, 0xf9, 0x71, 0xf7, 0x8d, - 0xbd, 0xf4, 0x30, 0x9f, 0x4f, 0x3f, 0xf9, 0x86, 0x10, 0xfe, 0x04, 0x44, 0x32, 0xb4, 0xdd, 0xfd, - 0xe7, 0xa5, 0x69, 0xa0, 0xf0, 0xc4, 0xb8, 0x53, 0xf9, 0x70, 0x93, 0x15, 0x8f, 0xb0, 0x8b, 0xf2, - 0x0c, 0x14, 0x47, 0xc6, 0xcf, 0xf0, 0x2d, 0x10, 0xc3, 0x6e, 0x27, 0x13, 0xd0, 0x1e, 0x5c, 0x01, - 0x03, 0x99, 0x10, 0x67, 0x97, 0xdb, 0x62, 0x7b, 0x7b, 0x0b, 0x49, 0xcd, 0x44, 0x5c, 0xda, 0x22, - 0x22, 0x5c, 0xe0, 0x96, 0xa8, 0x3a, 0x4b, 0x25, 0x5d, 0xee, 0xf3, 0x99, 0x97, 0xc1, 0xae, 0xca, - 0x89, 0x26, 0xe9, 0x4a, 0x47, 0x81, 0x56, 0x84, 0x37, 0x1d, 0xf2, 0xa1, 0x56, 0xae, 0x79, 0x19, - 0x18, 0x3f, 0xd2, 0x08, 0x93, 0xae, 0xae, 0xcf, 0x49, 0x7d, 0x4d, 0x66, 0x68, 0xf2, 0xf6, 0x0f, - 0x51, 0x66, 0x2f, 0x25, 0x63, 0xb9, 0x9f, 0x78, 0x33, 0xd8, 0x2b, 0x31, 0xe9, 0xe1, 0x0d, 0x2a, - 0x07, 0x16, 0xd3, 0xb5, 0xaf, 0x61, 0x4d, 0x08, 0x3d, 0xfb, 0x07, 0xfa, 0x36, 0x46, 0x6d, 0x22, - 0xa0, 0x1c, 0xc2, 0x4e, 0x1b, 0xbe, 0x7b, 0x69, 0x18, 0xb1, 0x41, 0x19, 0x44, 0x11, 0x9c, 0x63, - 0x84, 0x23, 0xce, 0x69, 0x2c, 0x0c, 0x08, 0x4e, 0x18, 0xd8, 0xce, 0x27, 0xf8, 0xe8, 0x62, 0x10, - 0x16, 0x41, 0x31, 0xca, 0x42, 0x51, 0x32, 0xed, 0x72, 0x5f, 0xce, 0xb0, 0xcc, 0x23, 0x11, 0x73, - 0x09, 0x6f, 0x82, 0x5f, 0x8c, 0x7c, 0x19, 0x18, 0x34, 0x30, 0x97, 0x59, 0x25, 0x44, 0x2d, 0xf8, - 0x06, 0xa2, 0x20, 0xa5, 0x6f, 0x88, 0x31, 0xce, 0x12, 0xf1, 0x30, 0xb6, 0x78, 0x36, 0x3c, 0xed, - 0x6d, 0x68, 0x6f, 0xc0, 0xe3, 0x00, 0x32, 0xcf, 0xd1, 0xc1, 0xe2, 0x6f, 0xd3, 0xc1, 0xe2, 0x66, - 0x1d, 0x0c, 0x44, 0x3c, 0x43, 0xf3, 0x42, 0x56, 0x9a, 0x48, 0x75, 0xab, 0xd4, 0x67, 0x08, 0x5a, - 0xfd, 0x5b, 0x2f, 0xf9, 0xaa, 0x97, 0x4b, 0x64, 0x72, 0xae, 0x60, 0x0d, 0x2e, 0xd2, 0xa4, 0xcf, - 0xb3, 0x98, 0xe0, 0xbf, 0xcb, 0xda, 0x85, 0xd4, 0xe7, 0xd8, 0xd1, 0x1f, 0xe5, 0x50, 0xa6, 0x13, - 0xf8, 0xc7, 0x1d, 0x5d, 0xe0, 0xb2, 0xce, 0x05, 0x2a, 0x5d, 0xd7, 0xfa, 0x3b, 0xa2, 0xd1, 0x70, - 0x58, 0x32, 0x02, 0x83, 0x21, 0x19, 0xfb, 0xc5, 0x82, 0x34, 0xa7, 0x21, 0x2c, 0x0f, 0x1c, 0x03, - 0x7d, 0x0a, 0xfd, 0x5a, 0x52, 0x29, 0xea, 0x4f, 0x50, 0x78, 0x47, 0xd9, 0x99, 0xc2, 0x59, 0x01, - 0x7f, 0xf9, 0x0f, 0xa0, 0x77, 0x2d, 0xd1, 0xed, 0x9e, 0x6e, 0x16, 0x2f, 0xfa, 0x36, 0x7f, 0xca, - 0x94, 0xd1, 0x73, 0x30, 0xe0, 0x17, 0x2f, 0x8f, 0x8d, 0x19, 0x24, 0x71, 0x1f, 0x67, 0x01, 0x83, - 0x5a, 0xd6, 0xc7, 0x8f, 0x46, 0x6f, 0x73, 0x06, 0x65, 0x1e, 0x16, 0xf9, 0xc9, 0x1d, 0xd9, 0x3f, - 0xd3, 0x35, 0x35, 0x0d, 0x9f, 0x20, 0x2e, 0x11, 0x90, 0x26, 0x61, 0xc5, 0x7d, 0x3a, 0xbf, 0xe6, - 0xd3, 0x81, 0xc3, 0xc3, 0xc2, 0xfa, 0x28, 0x5b, 0x10, 0xc4, 0x0d, 0xbc, 0xe5, 0x2e, 0x0c, 0xfd, - 0x0b, 0x7e, 0xe6, 0xd3, 0x26, 0xd0, 0x9b, 0xf5, 0xfd, 0x58, 0x71, 0x9a, 0x5c, 0x42, 0x25, 0xec, - 0xad, 0x6b, 0x4b, 0xc8, 0xd1, 0x05, 0xaa, 0x8d, 0xfd, 0x05, 0xb2, 0xfb, 0xbe, 0x1c, 0xd7, 0x72, - 0x39, 0xd0, 0x08, 0x9f, 0x8e, 0x12, 0x52, 0x2e, 0xe7, 0xb8, 0x0d, 0xd4, 0xe0, 0x8d, 0xc5, 0xa8, - 0xdf, 0x28, 0xe2, 0xbe, 0xbb, 0x43, 0x89, 0x0d, 0x95, 0x62, 0x4c, 0x4f, 0x83, 0xff, 0x36, 0x99, - 0x14, 0x45, 0xbd, 0x1a, 0xdd, 0x63, 0xfd, 0x66, 0xf3, 0x38, 0x9a, 0x78, 0x6a, 0x97, 0xe3, 0xb2, - 0x1f, 0x78, 0x75, 0xea, 0xb3, 0x3d, 0x58, 0xfd, 0xbc, 0xf9, 0x84, 0x3f, 0x85, 0x01, 0x29, 0x8c, - 0x31, 0xc1, 0xc5, 0x38, 0x0e, 0x93, 0xeb, 0x15, 0x37, 0x19, 0xd5, 0x8b, 0x13, 0x1c, 0xa1, 0x76, - 0x87, 0x41, 0x73, 0xdc, 0x44, 0x90, 0xa0, 0xdf, 0xe9, 0x88, 0x85, 0xf3, 0x35, 0x86, 0x03, 0x76, - 0x16, 0xc6, 0x39, 0x9e, 0xb0, 0x02, 0x33, 0xb3, 0xba, 0x98, 0xb4, 0x75, 0x71, 0x21, 0x89, 0x45, - 0x48, 0x18, 0x4c, 0xe1, 0x44, 0x00, 0xb2, 0xbb, 0xc8, 0xfb, 0xc3, 0xff, 0xba, 0xd5, 0x2d, 0x2f, - 0x58, 0x42, 0x23, 0x00, 0x34, 0x94, 0x37, 0x18, 0x9b, 0x79, 0x47, 0x1b, 0x59, 0x95, 0x5b, 0x17, - 0xeb, 0x63, 0x65, 0xad, 0x78, 0x8f, 0xe5, 0x6a, 0xd5, 0x76, 0xbc, 0x70, 0x89, 0x90, 0x6c, 0x4b, - 0x9f, 0x79, 0x99, 0xcf, 0x48, 0x7c, 0x68, 0x78, 0x6a, 0x62, 0x70, 0x4f, 0x7d, 0x2e, 0xd4, 0xe2, - 0xf7, 0x4d, 0x46, 0xb2, 0x66, 0x7d, 0x42, 0xa2, 0x5b, 0xed, 0xfd, 0xab, 0xf7, 0xa3, 0xb9, 0x78, - 0x98, 0x06, 0x4f, 0x8c, 0x0f, 0x90, 0x97, 0xa3, 0xe4, 0x01, 0x64, 0xf8, 0xe8, 0xfb, 0x9b, 0x5e, - 0x80, 0xeb, 0xad, 0x76, 0x7b, 0xe5, 0xf5, 0xb6, 0xd7, 0xf3, 0xdd, 0xce, 0xf3, 0xbd, 0xcd, 0xcd, - 0xde, 0x72, 0xbd, 0xb7, 0x3e, 0x1c, 0xeb, 0x85, 0xee, 0x4c, 0xce, 0x02, 0x91, 0x63, 0x36, 0xa9, - 0x45, 0xba, 0x0b, 0x3f, 0xb2, 0x7a, 0x30, 0x35, 0x26, 0x60, 0x54, 0xbf, 0x5b, 0x9f, 0x41, 0x13, - 0x10, 0xb1, 0x03, 0x96, 0xf9, 0xf0, 0xa5, 0xf0, 0x33, 0x6b, 0x8b, 0x8f, 0x56, 0xd1, 0xc2, 0x4d, - 0x91, 0xcf, 0x46, 0x47, 0x0d, 0x41, 0xd0, 0x65, 0x30, 0x85, 0xc4, 0xed, 0xaa, 0x47, 0x27, 0xab, - 0x07, 0x34, 0x78, 0xbc, 0x6d, 0x1c, 0x7a, 0x6c, 0x0c, 0x64, 0xf3, 0x28, 0xe4, 0x4d, 0x02, 0x8f, - 0x57, 0xc4, 0xf2, 0x69, 0xc1, 0x73, 0x1b, 0x46, 0xba, 0xfe, 0x2b, 0xe2, 0x5c, 0xd7, 0xc7, 0xf8, - 0x69, 0x71, 0x5f, 0x2b, 0x87, 0xb8, 0x51, 0xac, 0x9c, 0xb1, 0xb4, 0xdf, 0x16, 0xe9, 0xca, 0xda, - 0x42, 0xe7, 0x2a, 0xf4, 0xd3, 0x12, 0x45, 0x57, 0xa5, 0xb2, 0xd5, 0x8b, 0xf1, 0xfc, 0x49, 0x7d, - 0x7b, 0x08, 0x2c, 0xab, 0x06, 0xbf, 0xae, 0xab, 0xf9, 0x1f, 0x5c, 0x85, 0x4d, 0x82, 0x0a, 0x8d, - 0x3e, 0x9b, 0xe2, 0x0b, 0xcd, 0x87, 0x0e, 0x2c, 0x2d, 0x96, 0x68, 0xd0, 0x1a, 0x78, 0x58, 0x92, - 0x9f, 0x11, 0x74, 0xd8, 0x16, 0x72, 0x68, 0x3c, 0x83, 0xa2, 0x0f, 0xb5, 0x85, 0x64, 0x4b, 0xdc, - 0x00, 0x7a, 0x01, 0xc6, 0xbc, 0xe4, 0x74, 0x98, 0x69, 0xdc, 0x45, 0x7e, 0x12, 0xce, 0xa9, 0x83, - 0xcd, 0x02, 0xae, 0xf4, 0x2c, 0xec, 0xe5, 0xc4, 0x16, 0x9b, 0x46, 0x6b, 0x35, 0x03, 0x10, 0x0a, - 0xac, 0x2c, 0x6d, 0x35, 0xd5, 0x21, 0x54, 0x7f, 0x2d, 0x12, 0x56, 0x8b, 0xaf, 0x65, 0xef, 0x4d, - 0x48, 0x6b, 0xf9, 0x6e, 0x3a, 0x95, 0xdc, 0x5a, 0xf9, 0x73, 0x22, 0x03, 0xdd, 0xa1, 0x43, 0x20, - 0x8b, 0x79, 0x52, 0x3b, 0xbf, 0xbf, 0xc8, 0xf2, 0x3e, 0x3a, 0x58, 0x4f, 0x6f, 0xe7, 0xfd, 0x33, - 0xf4, 0x7e, 0xf1, 0x4a, 0xf3, 0x43, 0xff, 0x6c, 0xb7, 0x77, 0x0e, 0xf2, 0x22, 0xc6, 0xf1, 0xf5, - 0x7d, 0x6f, 0x8e, 0xc9, 0xc7, 0x50, 0xe0, 0xf4, 0x41, 0xd2, 0x34, 0x8e, 0xc7, 0x13, 0x60, 0xe6, - 0xb1, 0xc3, 0xe8, 0x2a, 0x45, 0xa1, 0xc3, 0x54, 0x71, 0xd7, 0x14, 0x86, 0x5d, 0x33, 0xec, 0x9a, - 0xc7, 0x51, 0x61, 0x12, 0x03, 0x0b, 0x46, 0x68, 0xa6, 0x67, 0xf9, 0x39, 0xdd, 0x7c, 0x8e, 0xd4, - 0xa7, 0x7e, 0x09, 0xb7, 0x4a, 0x65, 0xd0, 0x3f, 0x2a, 0x70, 0xf2, 0xf7, 0x12, 0x35, 0xb5, 0x5a, - 0xd2, 0xcd, 0xf0, 0xda, 0x92, 0xf0, 0x5e, 0x78, 0xf6, 0x33, 0xb1, 0xf3, 0x10, 0xfc, 0x8e, 0xfa, - 0x59, 0x22, 0xe9, 0x00, 0x5d, 0xf0, 0x5f, 0x87, 0xca, 0x3d, 0xdc, 0x32, 0x82, 0x18, 0x66, 0xf0, - 0xf4, 0xab, 0x93, 0xf8, 0xc3, 0x0a, 0x14, 0x5b, 0x1b, 0x4e, 0x0e, 0xbb, 0xc3, 0xc8, 0x40, 0x54, - 0xb8, 0x8b, 0x72, 0xfa, 0xe4, 0x4c, 0x8c, 0x73, 0xe5, 0x6f, 0x81, 0x20, 0x5d, 0x73, 0x7d, 0xda, - 0x49, 0x27, 0xe0, 0xab, 0x0a, 0xfb, 0x76, 0x8e, 0x79, 0xd0, 0x72, 0x38, 0xcc, 0x75, 0x9a, 0x13, - 0x1a, 0x7b, 0xd1, 0xea, 0x48, 0x45, 0x28, 0x96, 0xf1, 0x2e, 0x86, 0x71, 0xba, 0x03, 0x99, 0x9b, - 0x83, 0x2e, 0x60, 0xe2, 0xd2, 0x9d, 0xb1, 0x12, 0xfa, 0x26, 0xc0, 0xac, 0x1a, 0x29, 0xd7, 0xee, - 0x70, 0x6c, 0x2f, 0xd7, 0x6d, 0xf7, 0x9a, 0xa4, 0x67, 0x29, 0x88, 0x1f, 0x82, 0x08, 0x75, 0x73, - 0x15, 0x8d, 0x71, 0x87, 0x06, 0x14, 0x47, 0x89, 0x34, 0xc6, 0x0f, 0x2e, 0x28, 0x7c, 0xb9, 0xb8, - 0x32, 0x30, 0x7e, 0xe8, 0xeb, 0x2b, 0x74, 0x56, 0xfe, 0x04, 0x1a, 0x35, 0x7b, 0xd0, 0xae, 0x49, - 0x44, 0x63, 0x43, 0x87, 0x0d, 0xa7, 0xd3, 0x13, 0x91, 0x6c, 0xce, 0x58, 0xe7, 0x1c, 0xed, 0x5b, - 0x0c, 0x28, 0xa0, 0x87, 0xd9, 0x2f, 0x5d, 0xaf, 0xfc, 0x11, 0xb6, 0x80, 0xf9, 0xab, 0xf1, 0xd3, - 0x59, 0x71, 0xae, 0x57, 0x2e, 0x77, 0x49, 0x5b, 0x9b, 0xb2, 0x06, 0x35, 0x35, 0x68, 0x46, 0xf3, - 0x2d, 0x61, 0x71, 0xe3, 0x30, 0xe5, 0x2b, 0x43, 0xc3, 0x52, 0xd3, 0xf0, 0xbd, 0x5e, 0xdb, 0xd0, - 0xcd, 0x5f, 0x1a, 0xc6, 0x89, 0x15, 0x8c, 0xd1, 0xe8, 0x00, 0x6c, 0xa0, 0xf6, 0xe7, 0x3c, 0x6c, - 0xc1, 0x1c, 0x10, 0x46, 0x73, 0x95, 0x7e, 0xb9, 0x35, 0x77, 0x54, 0x68, 0x07, 0x9a, 0xb8, 0x6c, - 0x98, 0xd4, 0xec, 0xa1, 0x4e, 0x75, 0x2d, 0x75, 0x73, 0x6b, 0xcf, 0x7f, 0x99, 0xa8, 0x37, 0xa8, - 0x77, 0xf9, 0x65, 0xb3, 0x1e, 0x8d, 0x95, 0xde, 0xa4, 0xe3, 0x5f, 0x34, 0xbc, 0x71, 0xd9, 0x0b, - 0xec, 0xb0, 0x79, 0x93, 0x67, 0x5f, 0x8c, 0xb2, 0xc7, 0x1c, 0xe4, 0xd5, 0xf2, 0xe2, 0xc5, 0x6b, - 0xad, 0x96, 0x19, 0xf5, 0x30, 0x13, 0x1f, 0xe7, 0x9e, 0x81, 0x5f, 0x82, 0x26, 0xe3, 0x55, 0x6f, - 0x5b, 0x07, 0x69, 0xef, 0xa2, 0x25, 0x30, 0xb5, 0x5f, 0xf6, 0xb5, 0x42, 0x2f, 0x90, 0xc3, 0x28, - 0x9d, 0x52, 0xe9, 0x79, 0xeb, 0x9b, 0xc0, 0xee, 0xfd, 0x90, 0x4c, 0xcb, 0x66, 0xcf, 0x19, 0x20, - 0xd7, 0xf5, 0x2b, 0x1a, 0xd3, 0x89, 0x19, 0x3e, 0x21, 0x98, 0xa0, 0x48, 0xaf, 0xa3, 0xde, 0x1c, - 0x9f, 0xd1, 0xa8, 0x5a, 0xd0, 0xf7, 0x07, 0x15, 0x35, 0x65, 0x16, 0xcd, 0xf3, 0x02, 0xa1, 0x90, - 0x25, 0xc2, 0xa4, 0xf5, 0x81, 0x9f, 0x1e, 0xe5, 0xb1, 0x8d, 0xb5, 0x09, 0x0f, 0x33, 0x66, 0x3c, - 0x96, 0xa5, 0xfc, 0x69, 0x5d, 0x9c, 0xfe, 0xe9, 0xd5, 0xed, 0x6c, 0x06, 0x02, 0x3a, 0x8a, 0x01, - 0xed, 0x91, 0xf9, 0x25, 0x29, 0xe8, 0xa1, 0xf9, 0x48, 0x4d, 0x1c, 0x01, 0x13, 0xa1, 0x82, 0xca, - 0x89, 0x34, 0xc4, 0xe7, 0x6f, 0x18, 0x9b, 0xaf, 0xc7, 0xd6, 0xac, 0x1b, 0xb9, 0x08, 0xb2, 0x47, - 0x86, 0x38, 0xc3, 0x1c, 0xf6, 0x2c, 0x7e, 0xdc, 0x6c, 0x16, 0xd9, 0xaa, 0x69, 0x80, 0x7a, 0x0c, - 0xa3, 0x96, 0xe3, 0xff, 0xe6, 0xc1, 0x97, 0x6f, 0x84, 0x93, 0x4e, 0x03, 0x1a, 0x58, 0x5e, 0x75, - 0x14, 0xab, 0x2b, 0xa3, 0xf0, 0xd2, 0xc5, 0x2c, 0x1b, 0xc5, 0xf2, 0xaa, 0xe0, 0xad, 0xcf, 0x51, - 0x13, 0x96, 0xb5, 0xf9, 0xa1, 0x48, 0xde, 0xdb, 0xff, 0x9e, 0x44, 0x74, 0x5f, 0x1d, 0xff, 0x39, - 0x1e, 0x42, 0xfd, 0x1e, 0x4c, 0x16, 0x6f, 0x83, 0x4a, 0xba, 0x02, 0x42, 0x9b, 0x8a, 0x9b, 0xc1, - 0xbe, 0xd2, 0x40, 0xf9, 0x6d, 0xa7, 0x2e, 0xb6, 0x41, 0x09, 0xfe, 0xb7, 0x8b, 0x87, 0xbe, 0x36, - 0x06, 0xbe, 0xaf, 0x5a, 0x16, 0x9a, 0x2e, 0x56, 0xe1, 0xf9, 0x81, 0x4e, 0x38, 0xe8, 0x60, 0x6b, - 0x7c, 0x03, 0x71, 0x4b, 0x08, 0xbf, 0x86, 0xc0, 0xe2, 0x0f, 0x3f, 0xc3, 0x82, 0x29, 0x39, 0x45, - 0x40, 0x19, 0x96, 0xd2, 0x18, 0xde, 0x25, 0x72, 0xb9, 0x96, 0x0f, 0xb6, 0x14, 0x6d, 0xf1, 0x43, - 0xb3, 0xdc, 0x19, 0xd5, 0xe0, 0xaf, 0x0b, 0x10, 0xab, 0x4f, 0x62, 0x1a, 0xb0, 0x67, 0x89, 0xe0, - 0xd4, 0xcd, 0x70, 0x5d, 0xbf, 0x40, 0xcb, 0x32, 0x92, 0x88, 0xe3, 0x81, 0xe2, 0xa6, 0x5e, 0x47, - 0xd1, 0x1f, 0xe1, 0xd1, 0xb7, 0xa0, 0x6d, 0x8d, 0xe7, 0x32, 0xe7, 0xed, 0x3a, 0x82, 0xa6, 0x64, - 0x01, 0x63, 0xa2, 0x67, 0x41, 0xb2, 0xff, 0xae, 0xdd, 0x76, 0x8a, 0xb7, 0xc4, 0xd2, 0x06, 0x32, - 0xc6, 0x3b, 0xa6, 0x8d, 0x06, 0x97, 0xff, 0xee, 0xc1, 0x5d, 0x0c, 0x64, 0xd8, 0xe0, 0x6a, 0xd3, - 0x86, 0x61, 0xd5, 0xa0, 0x67, 0x13, 0xee, 0xdd, 0xc6, 0x80, 0x6a, 0x0c, 0x36, 0x81, 0x44, 0xe2, - 0xdd, 0x00, 0x55, 0x4d, 0xec, 0x87, 0xca, 0x6e, 0x2d, 0x41, 0xac, 0x2d, 0x1a, 0x7d, 0x7f, 0x85, - 0x41, 0xe6, 0x6b, 0x6c, 0x0c, 0xbb, 0x1c, 0x95, 0x36, 0x28, 0x95, 0x46, 0x60, 0xa9, 0xcd, 0xeb, - 0x6b, 0xdc, 0x34, 0xad, 0xbd, 0xa1, 0x79, 0x1e, 0x94, 0x38, 0x18, 0x4e, 0xcc, 0xf0, 0xe5, 0x31, - 0xba, 0xf8, 0x03, 0x29, 0x15, 0x2d, 0xeb, 0xda, 0x40, 0x39, 0x9d, 0xba, 0x66, 0xeb, 0x17, 0x98, - 0x99, 0x06, 0x13, 0x09, 0x8f, 0x78, 0x3e, 0xb4, 0x60, 0x47, 0x32, 0x2e, 0xe4, 0x55, 0x3b, 0x1a, - 0x63, 0x5f, 0x0b, 0x04, 0x03, 0x5d, 0x8d, 0xec, 0xd3, 0xab, 0xf4, 0xde, 0x92, 0x8a, 0x08, 0xd9, - 0xc7, 0x30, 0xf7, 0x0c, 0xae, 0xb9, 0xfd, 0xb3, 0x42, 0xbf, 0xbf, 0x8f, 0x8a, 0x2b, 0x4b, 0xde, - 0x87, 0xff, 0x0a, 0x9b, 0x46, 0xb8, 0x85, 0x89, 0xb2, 0x65, 0xdd, 0xc2, 0xd5, 0x40, 0x71, 0x93, - 0xbc, 0xc2, 0xde, 0xe1, 0xeb, 0x71, 0x5e, 0x32, 0x78, 0x9c, 0xd4, 0xd3, 0x53, 0xd1, 0x84, 0x21, - 0xb3, 0x29, 0x80, 0x8c, 0x24, 0x4d, 0x49, 0x03, 0x4d, 0xeb, 0x9e, 0xa5, 0xfb, 0x3a, 0x46, 0xf5, - 0x3e, 0x5f, 0x8c, 0x77, 0x27, 0x1f, 0xd1, 0x0d, 0x18, 0xd3, 0x30, 0x28, 0x86, 0xaa, 0x00, 0xfb, - 0xab, 0xcc, 0x5d, 0xb5, 0x0e, 0xb3, 0xc8, 0x40, 0xf1, 0x6f, 0x7f, 0x66, 0x4f, 0x7f, 0x66, 0xaf, - 0x12, 0x89, 0xda, 0xd0, 0x02, 0x48, 0x96, 0x36, 0x76, 0x91, 0x5a, 0x1f, 0x7f, 0x68, 0x3b, 0x64, - 0x4a, 0x4e, 0x33, 0xad, 0x9e, 0x38, 0xea, 0x8c, 0x29, 0x73, 0x9b, 0x68, 0x27, 0xcd, 0xfe, 0xa1, - 0x4f, 0x27, 0x4d, 0x4f, 0x9d, 0x34, 0xf8, 0xc2, 0x59, 0xbf, 0x9e, 0x61, 0x65, 0x39, 0x6c, 0x1b, - 0x1e, 0xb7, 0x7e, 0x6e, 0xc8, 0xb2, 0xd1, 0x95, 0xe5, 0x84, 0x73, 0x6c, 0x11, 0x26, 0xbc, 0xda, - 0x62, 0x20, 0xad, 0x2f, 0xb4, 0x06, 0x44, 0xa7, 0x92, 0x44, 0x05, 0x6d, 0x72, 0x1a, 0x42, 0x7b, - 0x09, 0x3f, 0x06, 0xed, 0xa5, 0x01, 0x7e, 0xc3, 0x27, 0xb4, 0xb3, 0x6e, 0x7c, 0xb4, 0x80, 0x3b, - 0x1d, 0xd6, 0xd9, 0x99, 0x0a, 0x88, 0x53, 0x85, 0xbe, 0x73, 0xe2, 0x50, 0xf9, 0xda, 0x91, 0x4a, - 0xec, 0x1d, 0x8e, 0xe0, 0x63, 0xed, 0x74, 0x1c, 0x63, 0xb0, 0x72, 0x78, 0x6e, 0x7f, 0x67, 0x83, - 0xc4, 0x0e, 0x7a, 0xb2, 0x3d, 0x7b, 0x78, 0x4c, 0x20, 0x0c, 0x3b, 0xc6, 0xcc, 0x9a, 0xc9, 0x0b, - 0x73, 0xc6, 0x88, 0x39, 0xdb, 0x63, 0x8e, 0xb3, 0x09, 0x72, 0x69, 0x49, 0x1f, 0xf8, 0x3b, 0x7f, - 0x63, 0x5a, 0x0f, 0x72, 0x8d, 0x4c, 0x9e, 0x04, 0xd4, 0x86, 0x13, 0x97, 0x99, 0x23, 0xb0, 0xab, - 0x8b, 0x8a, 0x14, 0xdd, 0x6a, 0x4e, 0xca, 0x6a, 0xc6, 0xa4, 0x3a, 0x3c, 0x7c, 0x4d, 0x94, 0xca, - 0x9a, 0xad, 0xfa, 0x7c, 0xdd, 0x34, 0x53, 0xd8, 0x1a, 0x03, 0xbb, 0x10, 0x33, 0x1c, 0x5f, 0xa2, - 0xb5, 0xd7, 0xe2, 0xb9, 0x4d, 0xf3, 0x85, 0xf2, 0xf4, 0xbf, 0xa8, 0x0d, 0x28, 0x46, 0x23, 0xd0, - 0xa3, 0x8a, 0x3b, 0x5f, 0x21, 0x56, 0x99, 0x32, 0x14, 0x6a, 0xa9, 0x8a, 0xc4, 0x3d, 0x01, 0xd2, - 0xd1, 0x28, 0x4f, 0x91, 0xea, 0xba, 0x14, 0xe1, 0xf4, 0x68, 0x37, 0x08, 0xb0, 0x49, 0x5d, 0x5e, - 0x12, 0x68, 0xd0, 0xba, 0xb8, 0xd4, 0x0a, 0x31, 0xa5, 0x99, 0x57, 0x4c, 0x42, 0x8e, 0x57, 0xee, - 0xbb, 0x8d, 0xa0, 0xa4, 0x0c, 0x1a, 0x81, 0xd3, 0x22, 0x81, 0x23, 0x04, 0x54, 0x6b, 0xf2, 0xc8, - 0x11, 0xab, 0xdd, 0x50, 0xe3, 0x8b, 0x52, 0x81, 0x9b, 0xeb, 0x00, 0xef, 0xfc, 0x0e, 0x46, 0xd5, - 0xe9, 0x2d, 0x4b, 0x44, 0x2a, 0xfd, 0x24, 0xa9, 0x98, 0xba, 0xa1, 0xd3, 0x9a, 0x41, 0x1b, 0x78, - 0xd9, 0xeb, 0xc3, 0xc3, 0x83, 0x2e, 0x67, 0x67, 0x7e, 0x77, 0x1f, 0x4e, 0x24, 0x96, 0xc1, 0x87, - 0x9e, 0x2e, 0x65, 0x93, 0x19, 0xa0, 0xf6, 0x66, 0x24, 0xeb, 0xab, 0x5a, 0x01, 0xf6, 0x7a, 0x4a, - 0xb8, 0x7d, 0xde, 0x38, 0xd4, 0x28, 0xfc, 0xe6, 0x51, 0x7c, 0xd9, 0x68, 0x10, 0x86, 0xe1, 0x80, - 0x8f, 0x25, 0xdf, 0xe0, 0xbd, 0x87, 0x53, 0xfd, 0x2a, 0x8b, 0x1b, 0x9a, 0x9e, 0xf1, 0xde, 0xa5, - 0x89, 0x76, 0xc5, 0x7b, 0x9f, 0xff, 0x53, 0xc7, 0xcf, 0x6b, 0xd9, 0x5e, 0x82, 0x37, 0x60, 0xfe, - 0x80, 0xa3, 0xde, 0x48, 0xf3, 0x6e, 0x12, 0x39, 0x4d, 0xb1, 0x56, 0x99, 0x72, 0x87, 0x34, 0xe8, - 0x2d, 0x34, 0x24, 0x88, 0xf9, 0xfb, 0xc6, 0xfc, 0xfd, 0xf3, 0x23, 0xbf, 0x34, 0xaa, 0x54, 0x7f, - 0x0b, 0x8a, 0x79, 0xd3, 0x35, 0xf4, 0xbf, 0x94, 0x09, 0x09, 0x3e, 0xbe, 0x11, 0x1b, 0x92, 0x96, - 0xff, 0x9e, 0xef, 0x57, 0x58, 0x12, 0xf9, 0x50, 0x1a, 0x5c, 0x49, 0xb7, 0x75, 0xf9, 0xae, 0x5b, - 0xb1, 0xd7, 0x3f, 0x87, 0xc3, 0x3e, 0xcf, 0x5c, 0xaf, 0xce, 0x96, 0xcd, 0x0c, 0xf6, 0xc7, 0x94, - 0x95, 0x57, 0xf1, 0x62, 0xcd, 0x64, 0xdf, 0xae, 0xaf, 0xb5, 0x3c, 0x38, 0xae, 0x3d, 0xf9, 0x1f, - 0x15, 0x55, 0xed, 0x1f, 0xa6, 0xae, 0x26, 0x9f, 0xad, 0x5e, 0x81, 0x71, 0x61, 0x50, 0x5e, 0xf8, - 0x93, 0x10, 0xb9, 0x2e, 0x6f, 0x1b, 0xc9, 0x9e, 0xa5, 0x11, 0x6c, 0xd0, 0x52, 0xad, 0xc1, 0x92, - 0x54, 0xac, 0x8d, 0xee, 0x13, 0x4d, 0xf7, 0x9b, 0x9a, 0x52, 0x93, 0xbe, 0xe8, 0xc1, 0x18, 0x34, - 0xdd, 0x34, 0xb3, 0x86, 0x77, 0x2c, 0x83, 0xa3, 0x8b, 0x16, 0xab, 0x5b, 0xa0, 0x62, 0x98, 0x2e, - 0x31, 0xc7, 0xeb, 0x0d, 0x90, 0x04, 0x9b, 0x9e, 0x62, 0x45, 0x67, 0x55, 0x5f, 0xae, 0xec, 0xa6, - 0x1d, 0x59, 0x4d, 0x01, 0x90, 0xab, 0xd5, 0x44, 0xdc, 0xfb, 0xfe, 0xd9, 0xf9, 0xb2, 0x82, 0xf3, - 0xc4, 0x11, 0xf5, 0x08, 0xe4, 0x09, 0xef, 0x01, 0x39, 0x9a, 0x0d, 0xe2, 0x63, 0x25, 0xe8, 0x57, - 0xde, 0x4f, 0x40, 0x90, 0x5f, 0x62, 0xee, 0xb6, 0xd2, 0xa7, 0xa7, 0x70, 0xab, 0x8f, 0xe1, 0x20, - 0x28, 0x9a, 0x0b, 0x14, 0x3e, 0x0a, 0xfb, 0x60, 0xd4, 0x47, 0x7b, 0xb0, 0x3a, 0xd3, 0xde, 0xe3, - 0x72, 0x49, 0x39, 0x66, 0xcb, 0xdd, 0xc3, 0x31, 0x1c, 0x57, 0x50, 0x83, 0xb6, 0xb8, 0xdc, 0x2f, - 0x21, 0x79, 0x1e, 0x42, 0x40, 0xfe, 0x2c, 0x84, 0x00, 0xb4, 0x30, 0xfa, 0x5b, 0x41, 0x2e, 0x2f, - 0x8d, 0x5a, 0x9b, 0x80, 0x62, 0xa6, 0x99, 0x72, 0xc3, 0xf6, 0x8a, 0x37, 0x91, 0x56, 0x2f, 0x0d, - 0x36, 0x5b, 0x26, 0x2f, 0x0a, 0xb4, 0xe5, 0x4d, 0xfa, 0x85, 0xc7, 0x31, 0x8b, 0xe1, 0x75, 0x15, - 0x69, 0xd6, 0xaf, 0x5d, 0xea, 0x26, 0x08, 0x12, 0x98, 0x03, 0x8f, 0xba, 0xeb, 0xc7, 0xde, 0x4d, - 0xd4, 0x0f, 0xbd, 0x34, 0xe9, 0x4b, 0xc4, 0x71, 0x76, 0xee, 0x8d, 0xe7, 0x51, 0x7f, 0xfd, 0x32, - 0x10, 0x74, 0xa2, 0x5a, 0x37, 0x78, 0xa7, 0xe9, 0x72, 0x39, 0x58, 0x85, 0xda, 0xa0, 0xe1, 0x1c, - 0xcc, 0x9f, 0x8b, 0x73, 0x30, 0x79, 0x26, 0xce, 0x81, 0x97, 0x6d, 0xd0, 0x20, 0x9d, 0x69, 0x6f, - 0x92, 0x28, 0x1d, 0x9e, 0x19, 0xcc, 0x3d, 0xfe, 0x19, 0xba, 0x0b, 0x26, 0xe2, 0x73, 0x3a, 0x0b, - 0xb2, 0x25, 0xff, 0x08, 0x34, 0x48, 0x0e, 0xdc, 0x22, 0xfb, 0x8c, 0xe9, 0xdb, 0x16, 0xe9, 0xb7, - 0x41, 0xe2, 0x36, 0xfa, 0xff, 0x29, 0x6a, 0xad, 0x90, 0x02, 0x99, 0x1a, 0xf2, 0xa7, 0xa7, 0xad, - 0x5a, 0x79, 0x7e, 0x14, 0x24, 0xee, 0x54, 0x6e, 0x65, 0xc2, 0xcd, 0x12, 0x44, 0xfe, 0x0d, 0xa4, - 0xb6, 0x39, 0x61, 0x84, 0xcf, 0x25, 0x8c, 0xf4, 0xb9, 0x84, 0x11, 0x3d, 0x93, 0x30, 0x62, 0x45, - 0x18, 0xa1, 0x17, 0x2b, 0xc2, 0x48, 0xc5, 0x67, 0x20, 0x8c, 0x68, 0xa9, 0x53, 0x40, 0xac, 0x53, - 0x80, 0x5a, 0xbf, 0x45, 0x89, 0x11, 0xaa, 0x0b, 0x4d, 0x98, 0x5b, 0x31, 0x61, 0x3c, 0xff, 0x23, - 0xe5, 0x58, 0xbc, 0xb9, 0x8d, 0x8b, 0x08, 0x44, 0xc9, 0x12, 0x30, 0x15, 0x7e, 0x11, 0xb7, 0x38, - 0xc0, 0xcc, 0x49, 0x94, 0x2a, 0x13, 0x21, 0x9d, 0x63, 0x26, 0x48, 0xd9, 0xf3, 0xee, 0xae, 0xd7, - 0xc6, 0x6f, 0xf1, 0xcd, 0xf8, 0x9c, 0x97, 0x1a, 0x7c, 0x9a, 0xf0, 0x8a, 0xdb, 0xf9, 0x34, 0xf2, - 0x87, 0xcd, 0x38, 0x5a, 0x95, 0x4f, 0x73, 0xe8, 0xe0, 0xf6, 0x9e, 0x81, 0xe7, 0x6c, 0xc4, 0x01, - 0x1b, 0xfa, 0x15, 0x69, 0x0e, 0xda, 0xfb, 0x06, 0x56, 0xb6, 0x55, 0xf2, 0x32, 0xc3, 0xdd, 0xb6, - 0xcc, 0x54, 0xd0, 0xde, 0xfc, 0xf9, 0xbc, 0xcf, 0xf4, 0xe8, 0x55, 0xc9, 0x48, 0x64, 0xca, 0x44, - 0xed, 0xa4, 0x5d, 0xcc, 0xe6, 0xff, 0xec, 0xdb, 0x85, 0x0d, 0xdb, 0x81, 0xc7, 0xce, 0x61, 0x5a, - 0x59, 0x87, 0x9f, 0xb1, 0x20, 0x5c, 0x32, 0x2d, 0x42, 0x9a, 0x05, 0x5a, 0x18, 0x21, 0x0f, 0xdd, - 0xd7, 0xc2, 0xa6, 0x85, 0xcf, 0x3a, 0x74, 0x8b, 0xc9, 0xde, 0x7b, 0x98, 0x01, 0xbe, 0xfd, 0x34, - 0x2e, 0xfe, 0xa6, 0xc6, 0x82, 0xff, 0x62, 0x7c, 0xc8, 0x88, 0xe9, 0x9b, 0xc0, 0x0c, 0xc0, 0xda, - 0xf9, 0x8e, 0xc7, 0xa2, 0x59, 0xd5, 0x38, 0xb9, 0xbe, 0x78, 0x29, 0x3b, 0x0a, 0x6a, 0x76, 0xe3, - 0xa6, 0x32, 0xa2, 0x6c, 0xa7, 0xc3, 0xf4, 0x48, 0xb2, 0xe6, 0x8c, 0xec, 0x8e, 0x2d, 0xbb, 0xd1, - 0x03, 0xe9, 0x0a, 0x0c, 0xf2, 0x5f, 0x19, 0x4f, 0x56, 0xed, 0xe6, 0x42, 0x75, 0x53, 0x8d, 0x28, - 0x63, 0xeb, 0xa2, 0xc9, 0xea, 0xb4, 0x31, 0x7b, 0xe8, 0x6b, 0x59, 0xa2, 0xab, 0x54, 0x25, 0x62, - 0x23, 0x9e, 0xb9, 0xce, 0x12, 0x6b, 0xb2, 0x31, 0x08, 0x6f, 0x93, 0xd5, 0x5e, 0xd9, 0xc1, 0x73, - 0xd7, 0x5c, 0x21, 0x5f, 0xfe, 0xce, 0x45, 0x37, 0x10, 0x34, 0x7f, 0xe7, 0xaa, 0x43, 0x5f, 0x7d, - 0x56, 0x5d, 0x6c, 0xdc, 0xbf, 0x95, 0xed, 0xbb, 0x7a, 0xd3, 0xd6, 0xa3, 0xa9, 0xdd, 0x2a, 0x57, - 0xc0, 0x80, 0x53, 0xa7, 0x89, 0x27, 0xe4, 0x0f, 0xeb, 0x3b, 0x36, 0xc2, 0x55, 0xdd, 0x06, 0x9e, - 0xa5, 0x82, 0x54, 0x1b, 0x1f, 0x11, 0x6d, 0xf0, 0x88, 0x5a, 0x98, 0x6b, 0xd3, 0x63, 0x3e, 0xa5, - 0x75, 0xbe, 0x88, 0xfc, 0x81, 0x55, 0xa9, 0x95, 0xa7, 0x50, 0x2c, 0xb5, 0x80, 0x2c, 0x87, 0x3a, - 0x03, 0x23, 0x93, 0x22, 0xf1, 0xa5, 0x34, 0xc1, 0x50, 0x1d, 0x3d, 0x5d, 0xf1, 0x27, 0x91, 0xd7, - 0x37, 0x93, 0x8e, 0x50, 0xf2, 0xe2, 0x46, 0xe4, 0xff, 0xa2, 0xd4, 0x5f, 0xed, 0xbc, 0x88, 0xac, - 0xc4, 0x64, 0x9b, 0x73, 0xb6, 0x9c, 0xec, 0xe3, 0x4a, 0x17, 0x01, 0x33, 0x1d, 0xb7, 0xeb, 0x3e, - 0x3d, 0x65, 0x1f, 0x8f, 0x7a, 0x38, 0x30, 0x68, 0xd8, 0x66, 0xce, 0x76, 0x3d, 0x1b, 0x14, 0x28, - 0x27, 0xfb, 0xdc, 0xa6, 0x28, 0x16, 0x0f, 0x4a, 0xd8, 0xa2, 0xae, 0x3e, 0x07, 0x68, 0xcf, 0x55, - 0x17, 0xfc, 0x76, 0xe9, 0xe3, 0xe5, 0x76, 0xb2, 0x8f, 0x32, 0x8e, 0x70, 0xd1, 0x22, 0xbf, 0x34, - 0xe9, 0x9e, 0x6e, 0x31, 0xc2, 0x50, 0x2b, 0xd1, 0x63, 0xe9, 0x77, 0xe0, 0x25, 0x7c, 0x3d, 0xf1, - 0x2f, 0x61, 0x9d, 0x43, 0xad, 0x68, 0xdc, 0x9a, 0x88, 0x7c, 0x6c, 0x7a, 0x7f, 0x24, 0xdd, 0xbc, - 0xb5, 0x6e, 0x6e, 0xd6, 0xd5, 0x84, 0xb5, 0xbc, 0xad, 0x49, 0x98, 0xa9, 0xf3, 0x8b, 0xb2, 0x48, - 0x1b, 0xc9, 0x4d, 0x73, 0x99, 0x21, 0x9a, 0x03, 0xa0, 0x24, 0x98, 0x96, 0x3b, 0xc8, 0xbd, 0xd2, - 0x3f, 0x48, 0x86, 0xa7, 0x63, 0xc0, 0xa1, 0xb3, 0x71, 0x46, 0x74, 0x99, 0xe6, 0x5c, 0x58, 0x8a, - 0x18, 0xdd, 0xeb, 0x51, 0x82, 0x68, 0xed, 0x72, 0x86, 0x9b, 0x8c, 0x78, 0x84, 0x63, 0xce, 0x7d, - 0xae, 0x7e, 0x9e, 0x39, 0xf6, 0xc2, 0x76, 0x87, 0xbb, 0xbd, 0xdf, 0xf7, 0xdc, 0xd3, 0xc7, 0xa4, - 0x08, 0x1f, 0x44, 0x94, 0x6c, 0x94, 0x58, 0x93, 0x5b, 0x10, 0x8c, 0x6e, 0x2c, 0x9c, 0xb8, 0x3e, - 0x02, 0xfe, 0x6c, 0x14, 0x8b, 0xcb, 0xc7, 0xf3, 0x21, 0xdb, 0xee, 0xbf, 0x64, 0xe2, 0xc2, 0x34, - 0x44, 0x71, 0x37, 0x59, 0x78, 0xc9, 0xac, 0x31, 0x03, 0xf5, 0x1b, 0x64, 0xbf, 0x74, 0x1a, 0xcd, - 0x1e, 0x71, 0x77, 0x51, 0xf0, 0x8e, 0xbc, 0x58, 0x58, 0x72, 0x7a, 0x81, 0x3f, 0x19, 0xee, 0x9f, - 0x20, 0xfb, 0x08, 0xc4, 0x00, 0x7a, 0xc2, 0xe7, 0x41, 0x9b, 0xce, 0x29, 0xee, 0x2a, 0xd5, 0x0b, - 0x8e, 0x55, 0x0a, 0x60, 0x04, 0x43, 0xe8, 0x7e, 0x8d, 0x83, 0xd8, 0xd8, 0xd4, 0x22, 0x51, 0x36, - 0x6e, 0x66, 0xbe, 0x8d, 0xb3, 0x8f, 0xf5, 0x7d, 0x9c, 0x20, 0xac, 0x6d, 0x3a, 0xe2, 0x8e, 0x93, - 0x67, 0xd9, 0xc7, 0xf3, 0x20, 0x31, 0xb3, 0xdc, 0x42, 0x11, 0x1f, 0x61, 0xbd, 0x38, 0xad, 0x17, - 0xdd, 0xd5, 0x8b, 0x28, 0xd1, 0x62, 0x5f, 0x7b, 0xc0, 0x02, 0x94, 0xd2, 0xcf, 0x1e, 0xe6, 0x84, - 0xb7, 0xdb, 0x96, 0x0e, 0xa3, 0xa8, 0x99, 0x48, 0xe8, 0x9d, 0xb0, 0xfb, 0xf8, 0x91, 0x78, 0xcc, - 0x54, 0xbe, 0xcb, 0xae, 0xbd, 0xf4, 0x88, 0x7c, 0x65, 0x0e, 0x5f, 0x7a, 0x10, 0x92, 0x33, 0x95, - 0xe2, 0x94, 0xbe, 0xc6, 0xc6, 0x6f, 0x5f, 0x31, 0x26, 0xf1, 0x6b, 0xec, 0xba, 0xf5, 0x84, 0xcf, - 0x9e, 0x6e, 0xbb, 0xd3, 0x24, 0x3e, 0xee, 0xad, 0x20, 0xe1, 0x82, 0x08, 0x60, 0x59, 0xfa, 0x0c, - 0xe2, 0x6f, 0xae, 0xca, 0x71, 0xaf, 0x97, 0x06, 0xbe, 0xa7, 0x7b, 0x57, 0xda, 0x9b, 0xfb, 0x37, - 0xd8, 0x1c, 0xf8, 0x01, 0x85, 0xfa, 0x13, 0x74, 0x67, 0xae, 0xf4, 0xdb, 0x7b, 0x4e, 0xbf, 0x12, - 0xee, 0x1b, 0x1d, 0xf6, 0xed, 0x92, 0xd7, 0xad, 0x64, 0x5f, 0x26, 0x51, 0x68, 0xcc, 0x5d, 0x0c, - 0xc8, 0x3c, 0x7f, 0x64, 0x47, 0xfd, 0x85, 0x71, 0x8c, 0xd3, 0x05, 0xdc, 0x3a, 0xa3, 0xde, 0x14, - 0x6d, 0x1c, 0x03, 0x63, 0x41, 0x27, 0xc9, 0x6c, 0xe4, 0x98, 0x0f, 0x80, 0x5a, 0x78, 0xc2, 0x99, - 0xf4, 0x04, 0xe3, 0xad, 0xbd, 0x3f, 0x72, 0x67, 0xe3, 0x16, 0xb7, 0x89, 0x88, 0xd2, 0x9d, 0xf9, - 0xbe, 0xfd, 0x9c, 0xf5, 0x92, 0xb7, 0x27, 0xc7, 0x69, 0x02, 0x7c, 0xea, 0x46, 0x68, 0x66, 0xd8, - 0x85, 0x36, 0xc0, 0xc0, 0x3c, 0x96, 0x15, 0x68, 0x06, 0xe2, 0x0b, 0xc3, 0xd7, 0x80, 0x0d, 0xea, - 0xb3, 0x36, 0xdd, 0x5a, 0x27, 0xba, 0x57, 0x2b, 0xa5, 0xdf, 0x2e, 0xdc, 0x64, 0x35, 0x8a, 0x44, - 0x71, 0x86, 0x7d, 0x9f, 0xaf, 0x44, 0x84, 0x28, 0x4e, 0x08, 0x5c, 0xcd, 0x99, 0x5f, 0x8e, 0x31, - 0xb9, 0x29, 0xc7, 0x0b, 0xf1, 0x08, 0xcb, 0xc3, 0xf5, 0x80, 0xe1, 0x4d, 0x10, 0x7c, 0x8d, 0xaf, - 0x0d, 0xe7, 0x1c, 0xdb, 0xdb, 0x55, 0x7c, 0xb2, 0x56, 0x1c, 0x8e, 0xdf, 0x14, 0xfe, 0x86, 0x40, - 0x14, 0xa1, 0xe1, 0x6c, 0x84, 0xa8, 0xf7, 0x9b, 0x4d, 0xea, 0x8b, 0x01, 0xd1, 0x4e, 0x0e, 0xe3, - 0xa8, 0xee, 0x5c, 0x61, 0x0a, 0x4f, 0xe5, 0x44, 0x79, 0x2c, 0x70, 0x95, 0xec, 0x39, 0xf0, 0xe9, - 0x00, 0x01, 0x60, 0x41, 0xe4, 0x5e, 0x5c, 0xf5, 0x7d, 0x2f, 0x87, 0xff, 0xee, 0xfa, 0x94, 0x5c, - 0xb9, 0x9b, 0xeb, 0xee, 0x98, 0x2a, 0x93, 0xae, 0x40, 0x00, 0xef, 0x1c, 0xfa, 0xee, 0x60, 0x9a, - 0x2e, 0x58, 0xf7, 0x4a, 0xaf, 0x76, 0xf0, 0xba, 0x52, 0xcf, 0x5d, 0xc2, 0x44, 0x62, 0xe6, 0x50, - 0x61, 0x38, 0xc6, 0x34, 0xba, 0x57, 0xbb, 0x34, 0x22, 0x84, 0x87, 0x75, 0x07, 0x7c, 0x70, 0x50, - 0xb8, 0x2c, 0x17, 0x96, 0x71, 0xb8, 0x14, 0x5c, 0x40, 0xc7, 0xaf, 0xe1, 0x9a, 0x96, 0xab, 0x28, - 0x82, 0xba, 0xf4, 0xf5, 0x86, 0x77, 0xe2, 0x35, 0xf9, 0x73, 0xf3, 0x25, 0xfa, 0x05, 0xdf, 0xb0, - 0x02, 0x02, 0x9a, 0x1b, 0xcb, 0x4a, 0xae, 0xb5, 0x4e, 0x7b, 0xdb, 0x3f, 0x22, 0x7a, 0x95, 0x6c, - 0x7a, 0x59, 0x69, 0xba, 0xbf, 0xb2, 0xe9, 0x7b, 0xbd, 0xe9, 0xb8, 0xd2, 0xf4, 0x40, 0x0a, 0x47, - 0xfa, 0x34, 0xae, 0xd8, 0xc3, 0x29, 0x61, 0x06, 0x74, 0xf3, 0xdb, 0x31, 0x47, 0x0f, 0x70, 0x7a, - 0x68, 0xa7, 0xd2, 0xa9, 0x62, 0xc0, 0x13, 0x1d, 0x27, 0x1d, 0x38, 0x62, 0x8b, 0xf4, 0x54, 0xd4, - 0x7a, 0xdd, 0x88, 0xa7, 0x08, 0x1d, 0x4e, 0x14, 0x69, 0x25, 0x2d, 0x15, 0x92, 0x59, 0x7b, 0x24, - 0xbe, 0x04, 0xe4, 0x3d, 0x10, 0xa0, 0x76, 0x71, 0xeb, 0x6c, 0xff, 0x4a, 0x6e, 0xeb, 0xfc, 0x49, - 0xb5, 0xad, 0x20, 0x1c, 0xd5, 0x81, 0xd6, 0x8c, 0xe9, 0xde, 0x32, 0xa0, 0x3c, 0xbd, 0x24, 0x0f, - 0x0b, 0x71, 0x6f, 0x2a, 0xc8, 0xd1, 0x4b, 0x83, 0x68, 0x9e, 0x76, 0x8f, 0x79, 0xfd, 0xfc, 0xee, - 0x4b, 0xfa, 0xcb, 0xe5, 0x18, 0xd3, 0xd3, 0x44, 0x01, 0x82, 0x18, 0xc2, 0x69, 0x9b, 0x76, 0xe7, - 0x1d, 0xdb, 0xc3, 0xbf, 0x97, 0xe2, 0xef, 0x18, 0x8f, 0x5f, 0x18, 0xcb, 0xef, 0xc1, 0x0d, 0x26, - 0x39, 0x3f, 0xcd, 0xd8, 0x1c, 0xb4, 0x0e, 0x1b, 0x21, 0x8a, 0x6d, 0x2f, 0x32, 0x11, 0x2e, 0xe3, - 0xf6, 0xdd, 0xfc, 0x27, 0xb5, 0xe4, 0xfa, 0xdc, 0xae, 0x59, 0x0c, 0x82, 0x42, 0x49, 0xd6, 0xb0, - 0xf4, 0x3c, 0xc8, 0xa7, 0xe9, 0x26, 0x6b, 0xd3, 0xf7, 0xf2, 0xda, 0xb5, 0xbd, 0xde, 0x01, 0xec, - 0x65, 0x4a, 0xee, 0x7a, 0xcd, 0x1e, 0x11, 0x2c, 0x6f, 0x7b, 0x1b, 0x91, 0x05, 0x7f, 0x62, 0x0f, - 0x8e, 0xc1, 0x4a, 0x45, 0x4c, 0x11, 0x6c, 0xfb, 0x86, 0x16, 0xca, 0xda, 0x5a, 0xb6, 0x50, 0x9d, - 0x34, 0x40, 0xcc, 0xd6, 0xc9, 0x6b, 0xa0, 0xd3, 0x68, 0xa9, 0x9a, 0x30, 0x8d, 0x94, 0x5f, 0xbb, - 0x1e, 0xd0, 0x29, 0x49, 0xcc, 0xe5, 0xae, 0xb7, 0xbf, 0x43, 0x4c, 0x95, 0xb2, 0x92, 0xef, 0x01, - 0x29, 0x0b, 0x09, 0x9a, 0x2c, 0x7f, 0x65, 0xc5, 0xd9, 0x2c, 0x0c, 0xe1, 0xc8, 0x71, 0x97, 0x1c, - 0x5f, 0x57, 0x7f, 0xa0, 0x5b, 0x82, 0x31, 0xf1, 0x01, 0xf8, 0x1a, 0x33, 0xd9, 0xaf, 0x28, 0x70, - 0x92, 0xdd, 0x88, 0xf3, 0x13, 0xf1, 0x52, 0x14, 0x91, 0xa1, 0xb5, 0x56, 0xf0, 0x0a, 0x50, 0x92, - 0x0c, 0xda, 0x64, 0x98, 0x0f, 0xa5, 0x5f, 0x29, 0x3a, 0xbe, 0x0a, 0xe1, 0xf8, 0x8b, 0x61, 0x2d, - 0xf2, 0x3b, 0x20, 0x3f, 0xf8, 0xcf, 0xaf, 0x2c, 0xe1, 0x5f, 0x61, 0x01, 0xd7, 0x36, 0x5a, 0xb1, - 0xab, 0x6a, 0x8e, 0xfb, 0xd8, 0xe9, 0x9f, 0xea, 0x9d, 0x3a, 0x0b, 0x4e, 0x61, 0xfd, 0xb5, 0x64, - 0xb9, 0xac, 0x74, 0x86, 0x5b, 0x4a, 0x5f, 0x69, 0x91, 0x79, 0x61, 0x05, 0x3b, 0x15, 0xfd, 0x78, - 0xed, 0x95, 0xfe, 0xb8, 0x49, 0xa5, 0xf7, 0xe5, 0x88, 0x2e, 0xcc, 0x77, 0x24, 0xdf, 0xc5, 0xa2, - 0x47, 0x56, 0x3e, 0x3c, 0x5e, 0x79, 0xf6, 0x08, 0x7a, 0xbb, 0x67, 0x88, 0xf8, 0x5b, 0x2d, 0xeb, - 0x35, 0x94, 0xed, 0x43, 0xd9, 0xda, 0xb5, 0xa7, 0xa4, 0x13, 0xfb, 0x5b, 0x64, 0x4d, 0x6c, 0x26, - 0xe2, 0xb5, 0xc7, 0xb6, 0x2b, 0x53, 0xdb, 0x54, 0x0e, 0xa5, 0x81, 0x3e, 0x9a, 0xe0, 0x0c, 0x64, - 0x0f, 0x10, 0x76, 0x2e, 0xe1, 0xbf, 0xb1, 0xa7, 0x3f, 0xe9, 0x5c, 0xe2, 0x84, 0x91, 0x34, 0x21, - 0x25, 0x49, 0x32, 0x6f, 0x40, 0x07, 0xfd, 0xb3, 0xb3, 0x73, 0x8f, 0xfe, 0x7f, 0x8e, 0xa6, 0x4f, - 0xb2, 0x77, 0x22, 0xc6, 0x18, 0xef, 0x54, 0x7f, 0x42, 0xbb, 0x74, 0xc9, 0x8a, 0xf7, 0x61, 0x8c, - 0x0e, 0x53, 0xcd, 0xb6, 0xdb, 0xc9, 0xa4, 0xa8, 0x1a, 0xe8, 0x70, 0x10, 0x57, 0x13, 0x5d, 0x7e, - 0x40, 0x90, 0xbe, 0x3f, 0xfe, 0x84, 0x30, 0x92, 0xfc, 0x82, 0x15, 0xbf, 0x4b, 0xe8, 0xc8, 0xbd, - 0xbd, 0xcb, 0xa8, 0xb8, 0xba, 0x1d, 0xe3, 0xed, 0xc5, 0xde, 0xbb, 0x68, 0x3e, 0x49, 0xd3, 0xf4, - 0x3a, 0x62, 0x7b, 0x08, 0x5b, 0xba, 0x77, 0x1f, 0x61, 0x86, 0xfb, 0xa5, 0x30, 0xe5, 0xcc, 0x61, - 0xc5, 0x0c, 0x04, 0x14, 0x19, 0x86, 0xee, 0x38, 0x57, 0x93, 0x4e, 0xd0, 0x7b, 0xe3, 0x0e, 0x0f, - 0x7c, 0x3c, 0xe1, 0xf0, 0xe9, 0xae, 0x77, 0x35, 0x19, 0xee, 0xcb, 0xaf, 0x07, 0x3e, 0xb2, 0x90, - 0x57, 0xaf, 0x82, 0xe0, 0x6a, 0x42, 0x25, 0x9d, 0xe0, 0x00, 0x4b, 0xfc, 0x37, 0x5a, 0x09, 0x74, - 0xd0, 0x78, 0xd0, 0x85, 0xf3, 0x42, 0xf1, 0x53, 0x2e, 0xc1, 0x5e, 0x5c, 0xe5, 0xe8, 0x05, 0x72, - 0x35, 0x59, 0x7a, 0x16, 0x46, 0xa5, 0x7b, 0xd6, 0xa1, 0xff, 0x07, 0xa4, 0x44, 0xd7, 0x7b, 0xdb, - 0x13, 0x20, 0x2b, 0xc0, 0x85, 0xe7, 0x06, 0x8e, 0x13, 0x14, 0xfc, 0x42, 0x06, 0x20, 0x0e, 0x51, - 0x89, 0xbf, 0xb7, 0x33, 0x48, 0x12, 0x9e, 0x29, 0x95, 0xd7, 0x40, 0x01, 0xa7, 0x36, 0xc8, 0xd0, - 0x4c, 0x97, 0xa1, 0xa5, 0x74, 0xfc, 0x0b, 0x1b, 0xa7, 0xa9, 0xd0, 0x57, 0x1c, 0x3e, 0x0c, 0x60, - 0x2d, 0x35, 0xa8, 0x4d, 0x50, 0xea, 0x02, 0x7b, 0x4f, 0x78, 0xf9, 0xc9, 0x11, 0x9f, 0x9a, 0xd0, - 0x53, 0x98, 0x1b, 0x6d, 0x05, 0x1b, 0x17, 0xd9, 0xc6, 0x06, 0x72, 0x3e, 0xa7, 0xee, 0xb3, 0x07, - 0xcb, 0x9f, 0x5f, 0x8e, 0xf5, 0x94, 0x50, 0x6a, 0xe4, 0x50, 0xbc, 0x5a, 0x47, 0xb3, 0x59, 0xa5, - 0x23, 0x5a, 0x53, 0x75, 0x6f, 0x63, 0x0f, 0xf4, 0x1b, 0xf6, 0x85, 0xc8, 0x21, 0xcb, 0xaf, 0x62, - 0x64, 0x4c, 0x28, 0x5d, 0x9a, 0x82, 0x66, 0xaf, 0xdd, 0xbf, 0xb3, 0xa0, 0x37, 0x60, 0xe2, 0xfe, - 0x9d, 0x55, 0xee, 0xdf, 0xc5, 0x35, 0x4e, 0xfb, 0xc5, 0x7b, 0x0d, 0xa5, 0x47, 0x9a, 0xa3, 0xab, - 0x79, 0x83, 0xf8, 0xe2, 0x88, 0x11, 0x12, 0x06, 0x2c, 0x28, 0x80, 0x73, 0xd0, 0xa7, 0x71, 0x61, - 0x31, 0x8f, 0x8c, 0x63, 0xdf, 0xc3, 0x20, 0xa1, 0xaf, 0x07, 0x5b, 0x84, 0x12, 0xe2, 0x91, 0x67, - 0xe0, 0x94, 0x16, 0xee, 0x40, 0xc2, 0x74, 0x51, 0x0d, 0xb4, 0x14, 0xdf, 0x21, 0x2c, 0xa3, 0xba, - 0xa4, 0x80, 0x6f, 0xf2, 0x35, 0xe8, 0x4f, 0x07, 0x6d, 0x2e, 0xe3, 0xcb, 0x0c, 0xec, 0x8a, 0x39, - 0xfa, 0x51, 0xb9, 0x34, 0xea, 0x2d, 0x96, 0xde, 0xa5, 0x32, 0xad, 0xf3, 0x09, 0xf9, 0x9e, 0x89, - 0x4a, 0xa4, 0x8d, 0x3c, 0xaf, 0x8d, 0xdc, 0xab, 0x40, 0x58, 0x2d, 0xb2, 0xbe, 0xde, 0xbf, 0x07, - 0xe3, 0xeb, 0xeb, 0x83, 0x5d, 0xd6, 0xd5, 0x10, 0x8f, 0x19, 0xe0, 0x3c, 0x0c, 0x21, 0x79, 0x8c, - 0xd4, 0x14, 0xd5, 0x01, 0xea, 0xf1, 0x47, 0x1c, 0x65, 0x0c, 0x1e, 0xf9, 0x30, 0x42, 0x5b, 0x45, - 0x00, 0x52, 0x82, 0x00, 0x19, 0x4b, 0x1a, 0x40, 0xc6, 0x92, 0xa5, 0x4a, 0xd4, 0x91, 0x98, 0x89, - 0x3a, 0x7e, 0x07, 0x6e, 0xd6, 0x37, 0x02, 0x66, 0x19, 0xaf, 0x41, 0x04, 0xce, 0x8a, 0x34, 0x2e, - 0xf0, 0x52, 0x8c, 0x45, 0x4c, 0xba, 0x19, 0xf4, 0x71, 0x94, 0x74, 0x6f, 0x46, 0xb5, 0xb5, 0xe8, - 0xf4, 0x60, 0x35, 0xfa, 0x85, 0xf3, 0xfb, 0x33, 0x80, 0x88, 0xbc, 0x68, 0x5a, 0x9e, 0x0b, 0x3a, - 0xb4, 0x40, 0x7b, 0xf8, 0x35, 0x03, 0x11, 0xf7, 0x38, 0x44, 0x9c, 0x61, 0x95, 0x68, 0x43, 0xa2, - 0x3f, 0xb5, 0x47, 0x8d, 0xd6, 0x32, 0xa3, 0xb9, 0xf5, 0x08, 0x55, 0x99, 0xd2, 0xe9, 0xa8, 0xa7, - 0xdc, 0x7c, 0x84, 0x63, 0x8d, 0x02, 0xc7, 0x36, 0x1f, 0x67, 0x7e, 0xab, 0xe7, 0x16, 0xd3, 0x53, - 0x8a, 0x45, 0x88, 0xe8, 0x51, 0xc6, 0x1d, 0x47, 0x32, 0xee, 0x38, 0x0f, 0x92, 0xb3, 0x08, 0x14, - 0xa4, 0xc6, 0xf4, 0x61, 0x94, 0xe3, 0x48, 0x58, 0x16, 0xbf, 0xb0, 0x87, 0xc2, 0x9c, 0xbe, 0xb2, - 0x56, 0x16, 0x68, 0x2a, 0x1d, 0xe5, 0x55, 0xe7, 0x22, 0xbb, 0x9f, 0x37, 0xfa, 0x1b, 0x69, 0xd7, - 0xd5, 0x46, 0x56, 0x38, 0x6d, 0xb1, 0x9f, 0xb3, 0xac, 0x22, 0xa5, 0x48, 0x57, 0x22, 0x55, 0x78, - 0x32, 0xa7, 0x1d, 0x86, 0x05, 0xce, 0xd2, 0xc9, 0xad, 0x81, 0x73, 0x82, 0xa8, 0x2b, 0x61, 0xf1, - 0x6e, 0x5e, 0x0a, 0xb3, 0x1e, 0x2d, 0xac, 0x0a, 0x4d, 0x46, 0x8e, 0x6e, 0xe6, 0x0c, 0x63, 0xe8, - 0xc6, 0xe9, 0xca, 0xe5, 0xa2, 0x6f, 0x03, 0x2e, 0x4e, 0xe7, 0x94, 0xa2, 0x22, 0x20, 0x69, 0x88, - 0xca, 0x83, 0xb3, 0xfc, 0x1c, 0x01, 0x55, 0x9d, 0x82, 0xd7, 0x13, 0x9d, 0xba, 0x47, 0x89, 0x2b, - 0xad, 0x2b, 0x98, 0x5c, 0x29, 0x3e, 0x4a, 0x76, 0x8b, 0x41, 0x8c, 0xf9, 0x03, 0xa9, 0x16, 0xb1, - 0x5b, 0xc6, 0xbd, 0x45, 0x77, 0x7b, 0xe7, 0x6e, 0xe3, 0x20, 0x34, 0x84, 0x3d, 0x20, 0x65, 0x03, - 0x72, 0xcf, 0x1c, 0x0e, 0x7a, 0x31, 0x1a, 0xf8, 0x7a, 0xfa, 0xa0, 0xb4, 0x48, 0x59, 0x73, 0x6c, - 0xd5, 0x71, 0x69, 0x15, 0xc5, 0xf0, 0x74, 0x3f, 0x52, 0x1c, 0x65, 0xb9, 0xa8, 0xd2, 0x35, 0x45, - 0x58, 0x24, 0x11, 0x27, 0xce, 0xf0, 0x19, 0xd0, 0xbe, 0x34, 0xaa, 0x80, 0x18, 0x7a, 0xcd, 0x6a, - 0x48, 0xeb, 0x65, 0xa3, 0xf5, 0x78, 0xf8, 0xd0, 0x05, 0xd3, 0xfa, 0xd0, 0x90, 0xe9, 0xf5, 0x5e, - 0x2a, 0x60, 0x6c, 0x7d, 0x59, 0xc0, 0xbf, 0x02, 0x57, 0xc1, 0x37, 0xd9, 0x7a, 0x05, 0x9f, 0x60, - 0x2c, 0xcb, 0xc5, 0xca, 0x61, 0x9a, 0x8e, 0x70, 0xe5, 0xb5, 0x08, 0x90, 0xd8, 0x2e, 0xf4, 0xdd, - 0x7a, 0xa7, 0x93, 0x60, 0xc6, 0x8e, 0x7a, 0xfe, 0x1d, 0xad, 0xef, 0xa7, 0xa7, 0x64, 0x2b, 0x30, - 0xb0, 0x93, 0x47, 0x4a, 0xa3, 0xdd, 0xc7, 0xa9, 0xe8, 0xea, 0x2d, 0x5e, 0x49, 0xb5, 0x2f, 0xb3, - 0x12, 0x21, 0x50, 0x8e, 0x54, 0x37, 0x70, 0x89, 0x3b, 0x92, 0x7e, 0xb1, 0x09, 0xc8, 0xf1, 0xe2, - 0x83, 0xb2, 0xcb, 0x7a, 0xe5, 0xc6, 0x51, 0xb5, 0x08, 0xf9, 0x08, 0xe8, 0x4e, 0x15, 0x88, 0xc0, - 0x56, 0xb7, 0x74, 0xb1, 0x55, 0x65, 0x41, 0x19, 0x84, 0x8e, 0x65, 0x65, 0xc0, 0x37, 0x7e, 0x43, - 0x48, 0x85, 0x5a, 0x5f, 0x08, 0xe3, 0x65, 0x74, 0x84, 0x21, 0xbe, 0xfe, 0x2a, 0xfa, 0x29, 0x65, - 0x23, 0xee, 0x26, 0x9b, 0x10, 0x97, 0xd7, 0x1c, 0x63, 0x61, 0xe7, 0xf4, 0x9f, 0xd1, 0x5c, 0xe6, - 0x62, 0xca, 0xc2, 0x2c, 0xfa, 0x2b, 0x48, 0xe0, 0x50, 0xd0, 0xf2, 0xf6, 0xb4, 0xdb, 0xad, 0x20, - 0xf7, 0x9a, 0xaf, 0x72, 0x9a, 0x70, 0x8a, 0x79, 0xeb, 0xca, 0x7d, 0x1e, 0x5d, 0x81, 0x72, 0x2f, - 0x54, 0x50, 0x48, 0xf4, 0x04, 0x2a, 0x09, 0xb1, 0xb3, 0xd2, 0xaa, 0xb7, 0xfa, 0xe2, 0x79, 0x1c, - 0xdf, 0xce, 0x9d, 0xf5, 0x98, 0xc7, 0x6b, 0xaa, 0xe9, 0x17, 0xc1, 0xbc, 0xea, 0x92, 0xe7, 0x21, - 0xf8, 0xef, 0xe3, 0xfa, 0x9d, 0x7e, 0x17, 0x13, 0x3d, 0x87, 0xb0, 0x05, 0xe6, 0xb6, 0xeb, 0x7d, - 0x0e, 0x5e, 0x51, 0x3e, 0xc6, 0x7b, 0x2f, 0xa2, 0x21, 0x07, 0xbe, 0xf7, 0xe0, 0x0b, 0x84, 0x54, - 0xc2, 0xcd, 0x3d, 0x85, 0x12, 0xdc, 0xd7, 0x38, 0x65, 0x4d, 0xf6, 0xbe, 0x4d, 0x38, 0xfe, 0x67, - 0x99, 0x38, 0x81, 0x47, 0x44, 0x4c, 0xbf, 0xa4, 0xb7, 0xb0, 0x44, 0xf9, 0xa8, 0x5a, 0x00, 0xfc, - 0xbf, 0xaf, 0xe5, 0xb7, 0xbb, 0x0a, 0xf3, 0x8f, 0xf3, 0x94, 0xce, 0x09, 0xec, 0x45, 0xe5, 0xe5, - 0x69, 0x48, 0xa8, 0x89, 0x08, 0xa9, 0x84, 0xdd, 0x8d, 0xd2, 0x72, 0xfe, 0x1b, 0xe8, 0x63, 0x8e, - 0x0d, 0x6d, 0xd5, 0xbd, 0x1b, 0xc8, 0xca, 0xe2, 0x43, 0x4f, 0x97, 0x7a, 0x27, 0xd7, 0x12, 0x6e, - 0x3f, 0x9b, 0x20, 0x66, 0x5f, 0x2d, 0x1b, 0x96, 0x32, 0x8a, 0xd3, 0xe1, 0x62, 0xe4, 0xac, 0x92, - 0x87, 0x9a, 0xaa, 0x02, 0x47, 0x97, 0x58, 0xb5, 0xdc, 0x51, 0x91, 0x58, 0xee, 0xd3, 0x93, 0x3e, - 0x8d, 0xa2, 0xf2, 0x3d, 0x81, 0xef, 0x0e, 0x2c, 0xa6, 0x5c, 0x2a, 0xe8, 0x0d, 0x93, 0x84, 0xfd, - 0x4d, 0x2d, 0x6c, 0xbb, 0xa9, 0xbf, 0x08, 0xc7, 0x13, 0x2e, 0xd4, 0xc1, 0xc1, 0xc9, 0x5f, 0xcc, - 0xb9, 0xc0, 0x31, 0xfe, 0x92, 0x66, 0xde, 0x7f, 0x1f, 0x37, 0x24, 0xce, 0x92, 0x60, 0xc6, 0x5b, - 0x8e, 0x7c, 0x5d, 0xbe, 0x6b, 0xc0, 0x50, 0xd0, 0xed, 0x00, 0x5f, 0x12, 0x5e, 0x63, 0x7b, 0xbb, - 0xb2, 0x34, 0xb5, 0x91, 0x26, 0x41, 0xb1, 0xfb, 0x80, 0x60, 0xed, 0x64, 0xef, 0x26, 0x51, 0x2f, - 0x71, 0xbd, 0x38, 0xe8, 0x38, 0xf9, 0xcb, 0x64, 0xef, 0x1e, 0x84, 0xc3, 0xf4, 0xc7, 0xe8, 0x81, - 0x4d, 0x9d, 0x7d, 0x77, 0xe0, 0x6f, 0x05, 0x88, 0xee, 0xc5, 0x47, 0x3b, 0xf4, 0x41, 0xa8, 0x3c, - 0xa2, 0xec, 0x50, 0xbc, 0xe0, 0x88, 0x52, 0x51, 0x60, 0x41, 0x3c, 0xec, 0xf6, 0xf6, 0xb7, 0xb7, - 0xbf, 0x75, 0xf2, 0xb0, 0x6d, 0xf9, 0xf2, 0x41, 0xd7, 0xb0, 0x0e, 0x8d, 0x66, 0xcd, 0xc8, 0x16, - 0xd4, 0xbc, 0x1b, 0xe4, 0x38, 0xdc, 0xde, 0x6e, 0x2c, 0x6d, 0x9c, 0xe1, 0x18, 0x98, 0xff, 0x75, - 0x2e, 0x46, 0x05, 0xb2, 0x7d, 0x5b, 0x1f, 0xa0, 0xc8, 0xc5, 0xee, 0xa6, 0x2b, 0xdd, 0x83, 0x8e, - 0xc4, 0xb6, 0xd1, 0x3d, 0x3d, 0x09, 0x89, 0x6f, 0x71, 0x1f, 0xd4, 0x70, 0x37, 0x9f, 0x0b, 0xe0, - 0x29, 0xf5, 0x11, 0x89, 0xe3, 0xd9, 0x80, 0xdb, 0x39, 0x68, 0xd3, 0x82, 0xf1, 0x12, 0x4f, 0xbe, - 0xd1, 0x9f, 0x18, 0x46, 0x32, 0x0f, 0xf2, 0xe3, 0x34, 0xc6, 0x39, 0xe2, 0x24, 0x58, 0xc7, 0xce, - 0x1e, 0xf0, 0x22, 0x48, 0x94, 0x8d, 0xaf, 0x1a, 0x31, 0xec, 0xd2, 0xa2, 0xd2, 0x89, 0x68, 0xc6, - 0xf1, 0x0a, 0x1d, 0x58, 0xe9, 0x57, 0x65, 0x1f, 0x45, 0x56, 0xf6, 0x2b, 0x00, 0x3c, 0x89, 0xce, - 0xea, 0xb0, 0x84, 0xa2, 0x9c, 0xe3, 0xf5, 0xa0, 0xa1, 0x8a, 0x13, 0xe4, 0xbb, 0x40, 0x50, 0xe6, - 0x3b, 0xaf, 0x59, 0x9d, 0xcb, 0x26, 0x37, 0xb6, 0x27, 0xaa, 0xb8, 0xe2, 0x43, 0xa0, 0xbe, 0xdf, - 0x1f, 0xf5, 0xf6, 0x31, 0x59, 0x9f, 0xe8, 0xe4, 0xe9, 0x69, 0x8b, 0x6c, 0x60, 0xa2, 0x14, 0x57, - 0x93, 0x7f, 0x7e, 0x7a, 0xba, 0x1f, 0x06, 0x5a, 0x21, 0xff, 0x82, 0xdb, 0x16, 0x6d, 0x40, 0x40, - 0x2b, 0x3c, 0x88, 0x43, 0xf4, 0x37, 0x12, 0x8f, 0xd9, 0xea, 0xf5, 0xc5, 0x93, 0x30, 0x79, 0x97, - 0x1a, 0x73, 0xf9, 0xf8, 0x0a, 0xa9, 0xf9, 0xed, 0x78, 0xad, 0x27, 0x30, 0x0d, 0x1d, 0x12, 0x90, - 0x77, 0xb1, 0x2e, 0x27, 0x56, 0x5a, 0x1a, 0x7c, 0xae, 0xe8, 0x5d, 0x88, 0x76, 0x30, 0x1c, 0xe5, - 0x68, 0x0b, 0x0b, 0x04, 0xf5, 0x46, 0xb6, 0x8f, 0x0a, 0xe1, 0x6d, 0x91, 0xda, 0xdf, 0xfa, 0x82, - 0xd5, 0xfe, 0xb8, 0x47, 0xc2, 0x55, 0x23, 0x44, 0xbb, 0x12, 0x74, 0xfd, 0x0a, 0xff, 0x90, 0x83, - 0x22, 0x50, 0xb9, 0xf6, 0x76, 0xa3, 0x9c, 0x2b, 0x88, 0xfa, 0x09, 0xb1, 0xbd, 0x6d, 0xa7, 0x54, - 0x08, 0x82, 0x0f, 0xaa, 0xaa, 0x98, 0x81, 0x0d, 0x86, 0x5c, 0x11, 0xa1, 0x75, 0xf0, 0x0e, 0x06, - 0x2c, 0xf9, 0x07, 0xc6, 0x32, 0x10, 0x5a, 0xbb, 0xdd, 0xae, 0x48, 0xbc, 0x23, 0x35, 0x30, 0x65, - 0xcc, 0x51, 0x29, 0x77, 0x0a, 0x95, 0x0c, 0x1c, 0xea, 0x69, 0x23, 0xd8, 0xde, 0x56, 0x5f, 0x40, - 0xb8, 0xc0, 0xa3, 0x86, 0xb7, 0x40, 0x17, 0x39, 0x2b, 0x71, 0xcb, 0x1f, 0x11, 0x1d, 0x67, 0x44, - 0x87, 0xcd, 0xd3, 0x93, 0xa9, 0xe0, 0x32, 0x6f, 0x01, 0xa5, 0x74, 0x5d, 0xed, 0x69, 0xc3, 0x82, - 0x32, 0x8f, 0x5a, 0xb9, 0xfd, 0xc6, 0xfa, 0xf8, 0xdb, 0x52, 0xd9, 0xc8, 0x6a, 0xf3, 0x59, 0x72, - 0x0e, 0xd1, 0xca, 0x83, 0x12, 0xdb, 0x83, 0x5d, 0x25, 0xb8, 0x47, 0x38, 0x9d, 0x7e, 0xc0, 0xbb, - 0x0d, 0x64, 0x45, 0x98, 0xe0, 0x97, 0xac, 0x70, 0xd0, 0xde, 0x26, 0xc4, 0x4f, 0x74, 0xfb, 0xc2, - 0x7e, 0xea, 0xb5, 0x6e, 0x52, 0xf4, 0x82, 0x4a, 0xef, 0xa1, 0x33, 0xe4, 0x17, 0xed, 0x15, 0x0b, - 0x3c, 0xa8, 0xe9, 0x90, 0x5d, 0x57, 0x93, 0xba, 0x4c, 0x6f, 0xa1, 0x1e, 0x9e, 0x29, 0x6b, 0xea, - 0xdd, 0x66, 0xeb, 0xaa, 0xd1, 0x83, 0x41, 0x70, 0x2c, 0xeb, 0xfd, 0x9f, 0xa3, 0x3d, 0xe0, 0xf2, - 0x51, 0x56, 0x0c, 0xad, 0xa3, 0x3d, 0x44, 0xf9, 0xc6, 0xbf, 0x57, 0xc5, 0x4d, 0x3c, 0xb4, 0xfe, - 0x2f, 0x26, 0xcb, 0x09, 0x21, 0xbc, 0xa3, 0x01, 0x00 + 0x9c, 0x32, 0x78, 0xfb, 0xdf, 0xc5, 0x5e, 0xff, 0x91, 0x58, 0x96, 0xe5, 0x9c, 0x86, 0x77, 0x38, + 0x82, 0x50, 0xf6, 0x81, 0x49, 0x94, 0xad, 0x2b, 0x90, 0x14, 0x08, 0x34, 0x11, 0x86, 0x3a, 0xb5, + 0x52, 0xb1, 0xe1, 0xb0, 0x88, 0x89, 0x47, 0x62, 0x86, 0xa0, 0x9f, 0xc4, 0x20, 0x42, 0x39, 0x0c, + 0x14, 0x92, 0xa0, 0x06, 0x9a, 0xd8, 0xad, 0xeb, 0x24, 0xbd, 0x07, 0x5e, 0x95, 0xa6, 0x53, 0x8b, + 0x00, 0x02, 0xfa, 0x34, 0x89, 0x9d, 0x23, 0xd4, 0x54, 0x43, 0xd0, 0x72, 0x2d, 0x50, 0x69, 0x89, + 0x4c, 0x90, 0xbf, 0x89, 0xb2, 0xa1, 0x1a, 0xd6, 0xd1, 0xf8, 0xb6, 0x28, 0xe8, 0x00, 0xa4, 0xc5, + 0x1e, 0x17, 0x89, 0x05, 0xff, 0xed, 0x66, 0x36, 0x68, 0xa2, 0x40, 0x01, 0x93, 0xeb, 0xc0, 0x16, + 0x24, 0x68, 0x0f, 0x81, 0x36, 0x1e, 0x0d, 0xca, 0x38, 0xda, 0xe3, 0xad, 0x87, 0x3b, 0xae, 0x47, + 0xcb, 0xb8, 0x07, 0xef, 0x0d, 0x16, 0xb1, 0xe1, 0x12, 0x17, 0x13, 0x26, 0xd8, 0x3a, 0x97, 0xc9, + 0xbd, 0x66, 0x89, 0x87, 0x08, 0x5a, 0x78, 0x45, 0x24, 0x15, 0x93, 0x00, 0xe7, 0x2e, 0x20, 0x4a, + 0x71, 0x4f, 0x27, 0xe1, 0x1b, 0x81, 0x17, 0xd0, 0x01, 0x29, 0x12, 0xf4, 0x79, 0xe8, 0x4b, 0x07, + 0xa0, 0x2c, 0x25, 0x2f, 0x31, 0x26, 0x9b, 0xa1, 0x20, 0x2c, 0xef, 0xa7, 0xe1, 0x2f, 0xcf, 0xbd, + 0x02, 0x52, 0x3e, 0x1a, 0x79, 0x40, 0x7c, 0x84, 0xa2, 0xad, 0x40, 0xfc, 0x0e, 0x72, 0x61, 0x59, + 0x51, 0xb0, 0x0d, 0x7b, 0x4f, 0x10, 0x0e, 0xd7, 0x28, 0x07, 0x40, 0xe6, 0x44, 0xc1, 0x17, 0x42, + 0xff, 0x7c, 0xb1, 0x20, 0xfd, 0x73, 0x69, 0x54, 0xbb, 0x70, 0x3d, 0xae, 0xaa, 0x16, 0xad, 0x1a, + 0xa8, 0x03, 0x9a, 0xc4, 0xf5, 0xd3, 0x53, 0x55, 0xac, 0xf1, 0x94, 0x66, 0xaa, 0xa9, 0xa6, 0xfc, + 0x1e, 0x9e, 0x79, 0xd5, 0xa9, 0xb5, 0xa8, 0xa6, 0xba, 0x98, 0x89, 0xb3, 0xd7, 0x55, 0x55, 0xcc, + 0xb2, 0xa5, 0x6d, 0x66, 0xee, 0x6f, 0x55, 0xaa, 0xa0, 0x2e, 0x3c, 0x0d, 0x6f, 0x75, 0x4a, 0xf9, + 0x8d, 0x01, 0x17, 0xc1, 0xe0, 0x1d, 0xda, 0xa4, 0x38, 0x34, 0x99, 0xf3, 0x23, 0xfb, 0xcb, 0xa7, + 0xe0, 0xec, 0xbc, 0xbc, 0x39, 0x95, 0x83, 0xfb, 0xcb, 0x27, 0xc5, 0xb8, 0x80, 0x39, 0xc0, 0x22, + 0x43, 0x45, 0xb9, 0xfb, 0x7d, 0x77, 0xc1, 0x90, 0x4a, 0x33, 0x49, 0x79, 0xfc, 0xc6, 0xc0, 0x1e, + 0xfe, 0x05, 0x6f, 0xdf, 0xe9, 0x6d, 0x1f, 0xed, 0x65, 0xb0, 0xe9, 0xf9, 0xda, 0xfb, 0xa6, 0x86, + 0x00, 0x1d, 0x51, 0x98, 0x3e, 0x74, 0x71, 0x51, 0xa1, 0xe0, 0x87, 0x87, 0x1c, 0x29, 0xd8, 0xca, + 0xf2, 0x22, 0xb7, 0x89, 0xf6, 0xe1, 0x94, 0x4b, 0xce, 0xfc, 0xf3, 0xe5, 0xd7, 0x78, 0xac, 0x51, + 0x35, 0xc9, 0xed, 0x14, 0x39, 0x2e, 0x7e, 0x76, 0x07, 0x28, 0x0e, 0x24, 0x67, 0xbd, 0xf3, 0xa5, + 0xa2, 0xeb, 0x0b, 0xaf, 0xd3, 0x29, 0x86, 0xaf, 0xe8, 0x35, 0xfb, 0x1e, 0x13, 0x9c, 0x02, 0x18, + 0x2e, 0x9c, 0xc0, 0x84, 0x1f, 0xa3, 0x4a, 0xbc, 0xe6, 0xd9, 0x80, 0x36, 0x27, 0xd9, 0x0d, 0x4d, + 0x67, 0xd9, 0xb0, 0x35, 0xbe, 0xc6, 0xc6, 0xc6, 0xd0, 0xe4, 0x84, 0xea, 0x5b, 0x66, 0x04, 0xfb, + 0xec, 0x70, 0x22, 0xd0, 0x0c, 0x27, 0xab, 0x19, 0xae, 0x2b, 0xcf, 0x2a, 0x7e, 0xc6, 0xf8, 0xe7, + 0x92, 0x9e, 0x41, 0x78, 0x97, 0x67, 0x21, 0x8c, 0x66, 0x8e, 0x11, 0x08, 0x5c, 0x94, 0x01, 0x75, + 0x21, 0x4f, 0xe7, 0x85, 0x33, 0xb9, 0xc9, 0x4e, 0x5c, 0xf9, 0x7a, 0x39, 0x6b, 0xc6, 0xf7, 0x2c, + 0xde, 0x44, 0x0c, 0x6f, 0xc2, 0x11, 0x4e, 0x20, 0x09, 0xbd, 0x0e, 0x7e, 0xec, 0x46, 0xb9, 0x08, + 0x4f, 0x88, 0x31, 0x1a, 0xac, 0x4c, 0x95, 0x82, 0x06, 0x58, 0x56, 0xca, 0xb6, 0x31, 0xc6, 0x82, + 0x0d, 0xb8, 0x95, 0x14, 0x6b, 0xc2, 0xc9, 0x37, 0x08, 0x61, 0x07, 0x03, 0x8d, 0xf0, 0x54, 0xbb, + 0xcc, 0x0b, 0xd1, 0x90, 0xa9, 0xb0, 0x33, 0x0a, 0x7c, 0xd9, 0x95, 0x83, 0x41, 0xbd, 0x61, 0xb6, + 0x4c, 0x6d, 0x78, 0x5d, 0xca, 0xf0, 0x91, 0x45, 0xe4, 0xbb, 0x59, 0x6d, 0x12, 0x29, 0x71, 0x10, + 0x39, 0xd5, 0x45, 0xbd, 0xd3, 0x17, 0x0b, 0x43, 0x14, 0x19, 0xd9, 0x59, 0x8c, 0x09, 0xd7, 0x81, + 0x09, 0x67, 0xf4, 0x77, 0xd9, 0x42, 0x42, 0x6c, 0xe9, 0x62, 0xcf, 0x95, 0xc6, 0x47, 0x91, 0xe8, + 0x77, 0x27, 0xc2, 0xad, 0x47, 0xac, 0x15, 0x3f, 0xed, 0x0c, 0xb7, 0xbf, 0x7b, 0x60, 0xbd, 0x83, + 0xb7, 0x83, 0xa3, 0xbd, 0x68, 0x88, 0x82, 0xf6, 0x52, 0x0a, 0x58, 0xa0, 0x52, 0xf3, 0xc1, 0x11, + 0x7b, 0xb6, 0x54, 0x0f, 0x36, 0xef, 0x81, 0x51, 0x7b, 0x6b, 0x16, 0xcf, 0x41, 0x24, 0x93, 0x3e, + 0x4b, 0x67, 0xac, 0xd3, 0xf3, 0xfd, 0x73, 0xd0, 0xe4, 0x1e, 0x32, 0xea, 0x8c, 0x2f, 0x4b, 0xce, + 0x2e, 0x19, 0x54, 0xc2, 0xdf, 0xf4, 0x61, 0xf3, 0x56, 0x8e, 0xfc, 0x05, 0xc6, 0x8d, 0x83, 0x39, + 0x78, 0x7b, 0x48, 0x83, 0x11, 0x8f, 0xd5, 0x96, 0x04, 0xba, 0x01, 0x99, 0x5d, 0xf6, 0xa8, 0x3a, + 0x1c, 0x6a, 0xc3, 0xe4, 0x1f, 0x71, 0x27, 0x5c, 0x90, 0x5b, 0x50, 0xa7, 0xb3, 0x04, 0x4a, 0xd8, + 0xe4, 0x18, 0x28, 0xa8, 0x3e, 0xb2, 0x03, 0xe2, 0xe0, 0xc4, 0x80, 0xb7, 0xb7, 0x7d, 0xf1, 0xd7, + 0x69, 0xbf, 0x48, 0x45, 0xcb, 0x10, 0x72, 0x73, 0xd7, 0x93, 0x64, 0x8d, 0x8e, 0x46, 0x2b, 0xea, + 0xd7, 0x2e, 0x5d, 0x39, 0xad, 0xbb, 0xd2, 0x08, 0x85, 0x8c, 0x5e, 0x92, 0x76, 0x0a, 0x0c, 0x27, + 0x3d, 0x52, 0x61, 0x43, 0x29, 0xda, 0xf3, 0x39, 0xf9, 0xe6, 0x67, 0xe9, 0xf9, 0xa0, 0xb2, 0xee, + 0x20, 0xf5, 0xf3, 0x25, 0xa5, 0xaf, 0xa4, 0x58, 0xf2, 0x30, 0x53, 0x83, 0xbf, 0xfa, 0xee, 0x40, + 0x58, 0xbf, 0xde, 0x39, 0xae, 0xa7, 0xf3, 0xc8, 0xfa, 0x86, 0x47, 0x57, 0x38, 0xdd, 0x85, 0x92, + 0xe0, 0x7d, 0x67, 0x73, 0xc6, 0xae, 0x58, 0x98, 0xed, 0xf5, 0xd8, 0xc1, 0x00, 0xd4, 0x6c, 0xe5, + 0xd0, 0xd8, 0x73, 0xc5, 0xe6, 0x64, 0x74, 0xd7, 0xde, 0xcd, 0xee, 0xe7, 0x98, 0x9e, 0xfe, 0x33, + 0x8f, 0x89, 0x98, 0x50, 0x8f, 0x53, 0x7b, 0x90, 0x0f, 0xa1, 0xe1, 0x28, 0x0e, 0x9c, 0x1c, 0xfe, + 0xbf, 0x17, 0xc0, 0x17, 0x57, 0x75, 0x01, 0xbf, 0x21, 0xf4, 0x7f, 0xcf, 0x05, 0xcd, 0xcf, 0x7a, + 0x67, 0xf7, 0x73, 0x72, 0x6f, 0xa0, 0xba, 0x87, 0xfe, 0x4b, 0xcd, 0x4d, 0x32, 0xdf, 0x03, 0x5d, + 0x06, 0x2b, 0xdd, 0xbc, 0xb3, 0xe5, 0xee, 0xe5, 0xcc, 0x9d, 0x75, 0x6f, 0xe9, 0x72, 0x84, 0xdc, + 0xcf, 0xce, 0x40, 0x31, 0x3b, 0x07, 0x46, 0x5d, 0x61, 0x2f, 0x58, 0xc7, 0x4d, 0x28, 0x22, 0xa1, + 0x13, 0x48, 0x2d, 0x0e, 0xaa, 0x92, 0x71, 0x1e, 0xb9, 0x45, 0xdf, 0x2c, 0xe7, 0x4f, 0x48, 0x03, + 0xfb, 0x4f, 0xb7, 0xb7, 0x57, 0xe1, 0xf5, 0xad, 0x0d, 0x92, 0x2e, 0xc8, 0x48, 0x5d, 0xb2, 0x5a, + 0xe5, 0xbf, 0x45, 0xc5, 0x95, 0x63, 0xfb, 0xdd, 0xde, 0x41, 0xd7, 0xe6, 0x31, 0x05, 0xf6, 0x97, + 0xf4, 0x3a, 0x42, 0x66, 0xdc, 0x9d, 0x24, 0x22, 0xc8, 0x60, 0x92, 0xf0, 0x4d, 0x7e, 0x07, 0x84, + 0x8b, 0x6d, 0x97, 0x16, 0xec, 0xf1, 0x74, 0xa9, 0x44, 0xbe, 0x23, 0x0a, 0x99, 0x53, 0x9b, 0x0c, + 0x9e, 0x5d, 0xd8, 0x48, 0xd4, 0x2f, 0x16, 0xe1, 0x92, 0xfe, 0xc8, 0xe1, 0xd8, 0xef, 0x6f, 0xa3, + 0x18, 0x6f, 0x48, 0xba, 0x77, 0x18, 0x07, 0x6c, 0xfe, 0x76, 0x0a, 0x9a, 0x44, 0x48, 0x08, 0x9d, + 0x44, 0x31, 0x58, 0xeb, 0x3e, 0x9a, 0x45, 0xdd, 0x9c, 0xca, 0x3b, 0xf6, 0x1f, 0x2c, 0x8a, 0xff, + 0xa6, 0xb2, 0x39, 0xe8, 0x1d, 0x9e, 0x6d, 0x4d, 0xdf, 0xdf, 0xb8, 0x76, 0xb5, 0x9f, 0x5f, 0x33, + 0x34, 0x06, 0x90, 0x5b, 0xbf, 0x6e, 0x18, 0xe8, 0xde, 0x52, 0xb9, 0x5b, 0xad, 0xfe, 0x23, 0x90, + 0x84, 0x85, 0x34, 0x01, 0x14, 0x02, 0x5d, 0x5e, 0xbf, 0xe7, 0x1d, 0xea, 0x55, 0x3e, 0xe4, 0x05, + 0x1a, 0x66, 0x40, 0xa6, 0x17, 0x7e, 0x89, 0xb6, 0x17, 0xd7, 0x2a, 0xfd, 0x38, 0x07, 0xd6, 0x93, + 0x5b, 0x7b, 0xa8, 0x6a, 0xa7, 0x09, 0x4d, 0x92, 0x28, 0x69, 0x96, 0xe5, 0xd5, 0x47, 0xfe, 0xf9, + 0xdd, 0xb1, 0x15, 0x4e, 0xa7, 0x73, 0xca, 0xda, 0xc8, 0xba, 0x37, 0xe1, 0xa4, 0x36, 0xa8, 0x28, + 0x66, 0xf9, 0x63, 0x0e, 0x3b, 0x0f, 0x2b, 0xcc, 0xf2, 0xee, 0x6d, 0xc7, 0xde, 0xc3, 0xd9, 0xc3, + 0xc7, 0xa2, 0x83, 0xa3, 0xc4, 0xc5, 0x30, 0x33, 0x56, 0xbf, 0xe4, 0x15, 0xf7, 0x78, 0x25, 0x97, + 0x60, 0x5b, 0xab, 0xfd, 0x7e, 0x48, 0xee, 0xa2, 0x79, 0x9a, 0xdc, 0xd0, 0x1c, 0x58, 0x37, 0x9c, + 0x4f, 0xae, 0xc8, 0x1a, 0x81, 0x6e, 0x28, 0x73, 0x06, 0x1f, 0x69, 0x89, 0xe3, 0xfb, 0x28, 0x43, + 0x5f, 0x25, 0x6a, 0x0d, 0x12, 0x2f, 0xbe, 0x5d, 0xe0, 0x4a, 0x75, 0x5e, 0x74, 0x7d, 0x67, 0x32, + 0xa2, 0xfa, 0x2e, 0x3c, 0x65, 0x97, 0x64, 0xb4, 0xd4, 0x77, 0x22, 0x9d, 0x62, 0xa0, 0xe2, 0x08, + 0xe7, 0x96, 0x81, 0xee, 0xce, 0x69, 0xfa, 0x70, 0x72, 0xd7, 0xcd, 0x41, 0x79, 0x5f, 0xe8, 0x0f, + 0xf2, 0x23, 0x07, 0xb9, 0xd2, 0x25, 0x87, 0x18, 0x12, 0xcc, 0x25, 0x47, 0xe6, 0x22, 0xbb, 0xe9, + 0x74, 0x44, 0x42, 0x6f, 0xaa, 0x77, 0x96, 0x9f, 0xd3, 0xf3, 0x42, 0xed, 0xd4, 0xc4, 0x20, 0xf4, + 0x81, 0x70, 0x4f, 0x3d, 0x0b, 0xcf, 0x83, 0xb8, 0x9b, 0x26, 0x5e, 0x88, 0xa6, 0xe7, 0xf2, 0xe9, + 0xc4, 0x38, 0xb5, 0xc1, 0x84, 0x9d, 0x9e, 0xeb, 0x85, 0x43, 0x1c, 0x11, 0xfe, 0x84, 0x03, 0x0b, + 0xeb, 0x47, 0x1f, 0x3c, 0x10, 0xa9, 0x1e, 0xdd, 0x83, 0x6f, 0x42, 0x50, 0x67, 0xd8, 0xe5, 0xc8, + 0x96, 0xfe, 0xad, 0xfc, 0x54, 0x11, 0xcc, 0x9e, 0x84, 0x1b, 0xd9, 0x8a, 0x6c, 0x19, 0x56, 0x3e, + 0xb9, 0xba, 0x8e, 0xc5, 0xef, 0xd6, 0x76, 0x32, 0xce, 0xb3, 0x81, 0x3c, 0xbf, 0xd0, 0x22, 0x6d, + 0x71, 0x53, 0x36, 0xd5, 0xc5, 0xf4, 0xbc, 0xe5, 0x61, 0x12, 0x2e, 0xe1, 0x11, 0x74, 0x38, 0x71, + 0xdc, 0x1c, 0x7c, 0x22, 0x8c, 0xcf, 0xc1, 0x5f, 0x5c, 0x1b, 0xc6, 0x83, 0x71, 0xff, 0xf1, 0x88, + 0x37, 0x15, 0xe3, 0x90, 0x67, 0x54, 0x0e, 0x4c, 0xd7, 0x18, 0xc6, 0x4d, 0x38, 0xe7, 0x43, 0xc1, + 0xc3, 0x09, 0x7f, 0x95, 0xa7, 0x13, 0x8d, 0x58, 0x7c, 0x69, 0x39, 0x50, 0xe7, 0xff, 0x34, 0x46, + 0x45, 0xdf, 0xcb, 0x23, 0x93, 0x20, 0x52, 0x6a, 0xf8, 0x9e, 0xc5, 0x65, 0x8c, 0x3b, 0xf0, 0x9f, + 0x8c, 0x0f, 0x77, 0x60, 0x5b, 0x64, 0x0b, 0x0d, 0x6c, 0x09, 0xe6, 0x81, 0xc3, 0x87, 0x9e, 0x24, + 0xe6, 0xa9, 0xf4, 0x32, 0x5a, 0xd2, 0x91, 0x8b, 0x37, 0xa0, 0x31, 0x41, 0xfd, 0xf0, 0xbf, 0xf0, + 0xe4, 0x20, 0x08, 0xe1, 0x10, 0x7f, 0xd5, 0x43, 0xb7, 0x25, 0x76, 0xb0, 0x7f, 0x68, 0x2f, 0xcb, + 0x33, 0xb9, 0xf2, 0xaa, 0x48, 0x1a, 0x69, 0x3e, 0xad, 0x93, 0xe2, 0xa1, 0x30, 0xc4, 0x14, 0x5c, + 0xd1, 0x0f, 0x0f, 0x62, 0x4d, 0x87, 0x38, 0xa8, 0x64, 0x04, 0xff, 0x81, 0xc2, 0xcc, 0x29, 0x1c, + 0x36, 0x51, 0xb8, 0x5a, 0xea, 0x98, 0x46, 0x05, 0x5f, 0x27, 0x4d, 0xe8, 0x08, 0xb9, 0xc0, 0x41, + 0x5f, 0x4c, 0xa9, 0x03, 0x1f, 0x94, 0x60, 0x1b, 0x6d, 0x14, 0xb0, 0x56, 0xf0, 0xb4, 0x44, 0x0e, + 0x02, 0x45, 0x8e, 0xfd, 0xc9, 0x6b, 0x6d, 0x7a, 0x9a, 0x34, 0xb1, 0x91, 0xcc, 0x13, 0xb6, 0xc8, + 0x3b, 0x61, 0xa3, 0xac, 0x13, 0x36, 0xca, 0x39, 0x35, 0x31, 0x67, 0xf3, 0xf9, 0xa9, 0xb5, 0xd7, + 0x09, 0x1c, 0xf5, 0x6b, 0x5b, 0x09, 0x9b, 0xf0, 0x1a, 0x80, 0x20, 0xe0, 0x9f, 0x24, 0x25, 0x20, + 0x18, 0xa3, 0x3d, 0xd4, 0x0b, 0x6f, 0xf1, 0x22, 0xf7, 0x26, 0x43, 0xe1, 0x3c, 0xb0, 0xd3, 0xd9, + 0x0c, 0x4e, 0xd5, 0xf0, 0x81, 0xb3, 0x85, 0xe0, 0x60, 0x9f, 0x27, 0x2b, 0x43, 0x91, 0x54, 0xbd, + 0x2f, 0x1c, 0x03, 0x99, 0x93, 0xaf, 0xd2, 0x18, 0x51, 0x52, 0x80, 0x27, 0xe2, 0x95, 0x2d, 0xd2, + 0x42, 0xb7, 0xdb, 0xb5, 0xf7, 0x9a, 0xe8, 0x61, 0x9c, 0xdb, 0xab, 0x65, 0x49, 0x10, 0x17, 0xd0, + 0xbc, 0xa7, 0x58, 0xca, 0xc8, 0x0e, 0x27, 0x45, 0x7d, 0xc2, 0x50, 0xcd, 0x94, 0x7d, 0xe1, 0x75, + 0x9e, 0xdc, 0xcf, 0xf5, 0xb5, 0xf5, 0xdf, 0xcc, 0xda, 0x64, 0x48, 0xba, 0xf0, 0xbb, 0x9f, 0x87, + 0x99, 0x15, 0xc1, 0x31, 0x79, 0xaf, 0xc6, 0x24, 0xd7, 0x4f, 0x7b, 0xd0, 0x78, 0x1e, 0xa9, 0x35, + 0x54, 0x2b, 0xa7, 0x33, 0x08, 0x7c, 0x32, 0x7a, 0x6a, 0x0a, 0x1e, 0x91, 0x26, 0xd4, 0x45, 0x60, + 0x8b, 0x0b, 0x97, 0x79, 0x18, 0x71, 0x5c, 0x54, 0x97, 0xd6, 0x33, 0xb0, 0xf7, 0x0f, 0x0f, 0xc9, + 0xee, 0x1c, 0xd8, 0x3d, 0xdb, 0xd2, 0x2f, 0xd5, 0xf4, 0x15, 0x86, 0xa7, 0xc2, 0x7c, 0xf7, 0xd4, + 0xb0, 0x6a, 0x83, 0x17, 0x7b, 0xda, 0x90, 0x7b, 0xad, 0xd6, 0xcf, 0x2d, 0x52, 0x05, 0xff, 0x6d, + 0x2e, 0x3f, 0x5a, 0x9a, 0x45, 0x1a, 0x66, 0x8f, 0x16, 0xe9, 0x53, 0x14, 0x6d, 0xac, 0x4f, 0x1f, + 0x7e, 0x20, 0x93, 0x74, 0x7b, 0xbd, 0x17, 0xa5, 0xf7, 0x1d, 0x77, 0x42, 0x1f, 0xd9, 0x9f, 0xb8, + 0xec, 0x01, 0xdb, 0xba, 0x48, 0x33, 0xec, 0xc2, 0x5e, 0xae, 0xe9, 0xe4, 0xe7, 0xd9, 0x0c, 0x96, + 0x53, 0xaf, 0x44, 0x36, 0xf0, 0x8d, 0x06, 0x2a, 0x5e, 0x9c, 0xf9, 0xa2, 0x2c, 0x64, 0x3f, 0x26, + 0x7b, 0x97, 0x4b, 0x9e, 0xdc, 0xde, 0x8c, 0xd9, 0x5c, 0xbc, 0x08, 0x5f, 0xbc, 0x1a, 0x58, 0x79, + 0x11, 0x04, 0xb1, 0xdb, 0x5b, 0x1a, 0xef, 0x83, 0x44, 0xbc, 0x65, 0xed, 0xed, 0xc2, 0x1c, 0xcb, + 0xf7, 0x7e, 0xcd, 0x1e, 0x11, 0xfe, 0x98, 0x9e, 0x45, 0x3b, 0x41, 0x12, 0xe3, 0x9a, 0x69, 0x6f, + 0x38, 0x74, 0xb6, 0xe9, 0xd0, 0x9d, 0xea, 0xab, 0x10, 0xc3, 0xef, 0x83, 0x26, 0x50, 0x99, 0x54, + 0x9a, 0xad, 0xa9, 0xfd, 0xbf, 0x38, 0xdf, 0x74, 0x56, 0x9d, 0xb0, 0x36, 0xf4, 0x74, 0xb6, 0x6a, + 0x70, 0xc3, 0x16, 0x1a, 0x92, 0x02, 0xd8, 0xef, 0xdd, 0x15, 0x7f, 0x04, 0x49, 0x31, 0x03, 0xbd, + 0x6d, 0xcd, 0x44, 0x4f, 0xf1, 0x46, 0x7e, 0x6d, 0xad, 0x77, 0xe8, 0x55, 0xf2, 0x6f, 0x25, 0xfa, + 0xcb, 0x79, 0xd6, 0x48, 0x3b, 0x3d, 0x9d, 0x23, 0x69, 0x8b, 0x0b, 0xf5, 0xff, 0x77, 0x5f, 0x7d, + 0x9e, 0x4d, 0x56, 0x12, 0x7b, 0x75, 0xc0, 0x50, 0xff, 0xdf, 0x36, 0xe0, 0xc6, 0x43, 0x6a, 0x92, + 0xcc, 0xd4, 0x80, 0x27, 0x95, 0x43, 0x9e, 0x9f, 0x08, 0xe6, 0x21, 0xef, 0xd3, 0x41, 0xb4, 0x19, + 0x55, 0x6a, 0xac, 0xfe, 0xca, 0x58, 0x15, 0xd8, 0x9c, 0x15, 0x76, 0xdf, 0x20, 0xfa, 0x82, 0x50, + 0xa8, 0x0b, 0xbf, 0xd6, 0x2f, 0x78, 0x73, 0x00, 0xca, 0xff, 0x54, 0x46, 0xa3, 0x98, 0x07, 0xdd, + 0x0a, 0x49, 0x18, 0x7a, 0xaa, 0x1c, 0x74, 0xd0, 0x97, 0x2e, 0x09, 0x43, 0x85, 0x46, 0x49, 0x78, + 0x73, 0x51, 0xd8, 0x94, 0x85, 0x37, 0x9a, 0xcf, 0x9f, 0x23, 0xb4, 0x5f, 0x58, 0x6c, 0x36, 0x83, + 0xe9, 0x6c, 0x3c, 0x97, 0x9b, 0xa8, 0x32, 0x95, 0x3f, 0x47, 0xfa, 0x4c, 0x6e, 0xa2, 0x7f, 0xf1, + 0x44, 0xb4, 0x77, 0x38, 0x65, 0xe5, 0xe0, 0x5b, 0x8c, 0xc3, 0x49, 0xba, 0xcb, 0x61, 0xf4, 0xd4, + 0x98, 0xe7, 0x9c, 0xa6, 0x8a, 0xa8, 0x40, 0xf9, 0xfd, 0x17, 0x96, 0xb1, 0xb0, 0xb0, 0x50, 0x03, + 0x8f, 0x2d, 0x74, 0x7a, 0x29, 0x89, 0x6d, 0x9e, 0xe9, 0xc4, 0x56, 0xa5, 0xd6, 0xb2, 0x63, 0x2e, + 0xe1, 0xee, 0x4f, 0x05, 0x1d, 0x0a, 0x83, 0xf2, 0x73, 0x87, 0x35, 0x35, 0x86, 0xf5, 0x03, 0xd9, + 0x71, 0xb5, 0xc1, 0x4c, 0x75, 0x65, 0x69, 0xdd, 0x60, 0xfc, 0x83, 0xef, 0x9b, 0x06, 0x53, 0x33, + 0xe3, 0xe1, 0x47, 0xcd, 0xa0, 0xd7, 0x60, 0xb2, 0xc6, 0x3d, 0x58, 0x37, 0xe4, 0xe9, 0x2a, 0xe7, + 0x30, 0xe0, 0x21, 0x8c, 0x23, 0xa7, 0xb1, 0xf5, 0x2d, 0xac, 0xab, 0xd1, 0x7a, 0xc7, 0x78, 0xf1, + 0x57, 0xf6, 0xf0, 0xcf, 0xe1, 0x43, 0x74, 0x73, 0x7b, 0x63, 0x71, 0x6e, 0x84, 0x36, 0xff, 0x5c, + 0x68, 0xe3, 0x40, 0xa0, 0x21, 0x50, 0xc6, 0xb4, 0x2b, 0xc8, 0x61, 0x47, 0x0f, 0x2f, 0xf5, 0xdd, + 0xbe, 0xfa, 0x86, 0x50, 0x44, 0x9a, 0x0b, 0x9b, 0x1e, 0x84, 0x5a, 0x3a, 0x1c, 0x85, 0xa0, 0x96, + 0x87, 0x47, 0x01, 0x2e, 0xe3, 0x20, 0x44, 0x4d, 0xbc, 0x64, 0x65, 0xa1, 0x72, 0xe1, 0x21, 0x89, + 0xb2, 0x7e, 0x7d, 0x5e, 0xca, 0xab, 0x17, 0x98, 0xbf, 0x91, 0xec, 0x83, 0x8d, 0xd3, 0x9d, 0x83, + 0x7e, 0xc5, 0xfd, 0x1f, 0x99, 0xf0, 0x96, 0x11, 0xc2, 0xa4, 0x70, 0xa2, 0x71, 0x97, 0x52, 0xff, + 0x3f, 0xda, 0x6f, 0xbc, 0x14, 0x43, 0x4a, 0xb0, 0x3b, 0x38, 0x48, 0xb7, 0xb9, 0x03, 0x6f, 0x4b, + 0x9f, 0x76, 0x55, 0xc0, 0x50, 0xd6, 0x83, 0x96, 0x39, 0x60, 0xc7, 0xcb, 0x5c, 0x7a, 0x1a, 0xb8, + 0x20, 0x88, 0x74, 0x36, 0x6a, 0xc2, 0x54, 0x93, 0x23, 0x29, 0x0b, 0xb5, 0x0d, 0x7f, 0xde, 0x32, + 0x7c, 0xa1, 0x18, 0x37, 0x79, 0xda, 0xcd, 0x73, 0xd8, 0x13, 0x76, 0xb5, 0x85, 0x5c, 0xa9, 0x61, + 0xaf, 0xa6, 0x55, 0xd7, 0x0c, 0x38, 0x1f, 0x88, 0x67, 0x69, 0xf6, 0x9b, 0x1d, 0x53, 0xe1, 0x43, + 0x13, 0x12, 0x66, 0x6d, 0x18, 0xb6, 0x2b, 0x71, 0xbc, 0x52, 0x45, 0xeb, 0x3a, 0x15, 0x85, 0xea, + 0xfc, 0xe3, 0xb5, 0xa4, 0xd2, 0x81, 0x4a, 0x44, 0x75, 0x2f, 0xf2, 0x1a, 0x74, 0x96, 0x89, 0xdd, + 0x18, 0xf6, 0xf8, 0x6e, 0x6c, 0xae, 0x38, 0x41, 0xcc, 0x9b, 0x98, 0xd7, 0xd7, 0xae, 0x62, 0xa9, + 0xf4, 0x54, 0x7f, 0x1a, 0x3f, 0xef, 0x84, 0xe2, 0xc5, 0xb7, 0xed, 0x0e, 0xfa, 0x03, 0x5c, 0x45, + 0x33, 0x34, 0x51, 0x20, 0x9d, 0x73, 0xcf, 0x77, 0x7f, 0x50, 0x94, 0x59, 0x41, 0x0b, 0x20, 0x75, + 0x02, 0x08, 0x5b, 0x44, 0xd3, 0xbe, 0x7a, 0xd7, 0x85, 0xdb, 0xe9, 0x79, 0xa8, 0x4c, 0x12, 0x78, + 0xd8, 0x12, 0xbb, 0xa1, 0x6b, 0x20, 0x20, 0xa5, 0x70, 0xce, 0x5c, 0x4a, 0xef, 0xcc, 0x3b, 0xc6, + 0x66, 0x3e, 0xaf, 0x6a, 0x9f, 0xa2, 0x6b, 0x2b, 0xfa, 0x8d, 0x11, 0xe4, 0x6d, 0x5e, 0xc0, 0x48, + 0x1f, 0xed, 0x65, 0xf9, 0xe8, 0x86, 0x84, 0xa4, 0x45, 0x27, 0xb8, 0x44, 0xb7, 0x42, 0xdc, 0x69, + 0x51, 0x4e, 0x56, 0xfa, 0x9f, 0x8a, 0x9b, 0x18, 0xe3, 0x81, 0x6d, 0x0f, 0xd3, 0x94, 0x22, 0x48, + 0x2e, 0xfd, 0xc5, 0x47, 0x78, 0x78, 0x7e, 0xfc, 0x0d, 0x5d, 0xa7, 0x78, 0x19, 0x3d, 0x09, 0xfa, + 0xa7, 0xd8, 0xe1, 0xd5, 0x26, 0xbc, 0x13, 0x11, 0xcf, 0x4c, 0x57, 0x67, 0xff, 0xc9, 0x45, 0xf9, + 0x41, 0xc2, 0xb5, 0xd6, 0x96, 0xe5, 0xff, 0x2f, 0x4a, 0xfc, 0x16, 0x22, 0x10, 0x41, 0xe8, 0x6d, + 0x94, 0x70, 0x22, 0x7f, 0x36, 0xac, 0x97, 0x31, 0xf4, 0x90, 0x91, 0x04, 0x25, 0x6c, 0x70, 0x2f, + 0x16, 0xd0, 0x3b, 0xd4, 0x3d, 0x81, 0xc2, 0xe3, 0x1c, 0x28, 0x80, 0x77, 0xe6, 0xaa, 0x9b, 0xab, + 0x0b, 0x83, 0x9a, 0x44, 0x98, 0x79, 0x33, 0x39, 0xcd, 0xd9, 0x74, 0x1e, 0xde, 0x8b, 0xce, 0x1c, + 0x79, 0x17, 0xd4, 0xe0, 0xb9, 0x6b, 0x7f, 0x27, 0xfa, 0xb1, 0xba, 0x38, 0x20, 0x7b, 0x25, 0xad, + 0x2d, 0xf8, 0xc2, 0x20, 0x59, 0x75, 0xd1, 0xa3, 0x04, 0xe6, 0x46, 0xe0, 0xd0, 0xbc, 0xc4, 0x84, + 0x9d, 0xb2, 0xbb, 0x6a, 0x82, 0xee, 0x00, 0x79, 0x7c, 0xce, 0xf9, 0x62, 0x60, 0x4e, 0x12, 0xb1, + 0x2d, 0x74, 0x07, 0x36, 0x63, 0x01, 0xe8, 0x7a, 0x56, 0x8f, 0xf0, 0x2f, 0xfd, 0x0d, 0xcb, 0x32, + 0x0c, 0xad, 0x25, 0x54, 0x15, 0xe9, 0x35, 0x25, 0x6d, 0x99, 0x16, 0x31, 0xd9, 0x41, 0x8f, 0xb2, + 0x43, 0x09, 0xb4, 0x29, 0x42, 0xe4, 0x08, 0x0a, 0xbc, 0xbe, 0xa9, 0x64, 0x10, 0xc0, 0xcb, 0x1c, + 0xf1, 0x3b, 0x5e, 0xcd, 0x51, 0x66, 0x44, 0xe1, 0xa5, 0x2a, 0xae, 0x90, 0xf9, 0x1a, 0xfa, 0x03, + 0x56, 0x02, 0x34, 0x32, 0x5c, 0x14, 0x0e, 0x5f, 0x90, 0x07, 0xe8, 0xcc, 0x4b, 0x87, 0x6a, 0xec, + 0x85, 0x5e, 0xea, 0x45, 0x70, 0x6c, 0xe3, 0xc0, 0xcc, 0xe7, 0xe4, 0xae, 0x1b, 0x05, 0x88, 0xe0, + 0x82, 0x59, 0xec, 0x5e, 0xe2, 0xed, 0x5b, 0x1c, 0x20, 0xc8, 0x95, 0x17, 0xc2, 0x9f, 0x7d, 0x8c, + 0xab, 0xc9, 0xcf, 0x0e, 0xce, 0x07, 0x74, 0x1b, 0x07, 0x8d, 0xed, 0xb9, 0x1d, 0x04, 0xb9, 0x1b, + 0xe3, 0x78, 0x5e, 0x1a, 0x69, 0xbd, 0xa0, 0x41, 0xbd, 0x2c, 0xad, 0x97, 0xa9, 0xae, 0x44, 0x42, + 0x09, 0x75, 0x2d, 0xd8, 0x3b, 0xdf, 0xed, 0x0d, 0xe2, 0x40, 0x80, 0x04, 0x33, 0x8a, 0xea, 0x09, + 0xb5, 0xaf, 0x3d, 0x1c, 0x4c, 0xf9, 0x75, 0xff, 0x7c, 0xb9, 0x85, 0x28, 0xb4, 0x18, 0x9e, 0x13, + 0x05, 0x6c, 0x4f, 0xae, 0xc1, 0x4b, 0x82, 0x63, 0xe1, 0x78, 0x87, 0xce, 0x05, 0x46, 0x66, 0xc3, + 0x59, 0xbb, 0xf4, 0x50, 0xce, 0xf0, 0xf0, 0xde, 0xca, 0xc5, 0x1b, 0x91, 0xe5, 0x1f, 0x2e, 0x24, + 0x84, 0xc9, 0x85, 0x06, 0xca, 0x6a, 0x55, 0x43, 0x8f, 0x25, 0xf0, 0xaa, 0x87, 0xa6, 0xe6, 0xff, + 0x49, 0xa3, 0x04, 0xfd, 0x44, 0x06, 0x17, 0x26, 0x85, 0xd4, 0x37, 0x20, 0xb9, 0x55, 0x7a, 0x88, + 0x96, 0x0c, 0x1c, 0x37, 0x34, 0xfc, 0x2b, 0x2b, 0x1b, 0x8e, 0xa4, 0xd6, 0xd9, 0x03, 0xfe, 0xcb, + 0x05, 0x55, 0x24, 0xe3, 0x5d, 0x94, 0x5d, 0xd1, 0xb1, 0x12, 0xd9, 0xc5, 0x31, 0x67, 0x17, 0x2f, + 0x16, 0xf9, 0xd2, 0xc1, 0x32, 0x97, 0x44, 0x73, 0x53, 0xef, 0xc0, 0xe1, 0xa6, 0xd0, 0x4d, 0xa9, + 0x76, 0x18, 0x5c, 0x90, 0x7e, 0xd6, 0xb4, 0x4f, 0xec, 0x18, 0x99, 0x01, 0x7e, 0x66, 0xc2, 0x0e, + 0x6b, 0x88, 0x8f, 0xd4, 0x00, 0xf5, 0x06, 0x5d, 0x65, 0x28, 0xf5, 0x05, 0xa3, 0x2e, 0xce, 0x42, + 0x99, 0xd2, 0xd1, 0x13, 0x14, 0x7e, 0x97, 0x6d, 0x70, 0xe5, 0xf1, 0x2b, 0x31, 0x8b, 0x72, 0xcd, + 0xc6, 0x38, 0x2c, 0xf2, 0xae, 0xe1, 0x39, 0xb9, 0x44, 0x3a, 0x2e, 0xeb, 0x60, 0xbf, 0x2f, 0xb6, + 0xcc, 0x87, 0xd3, 0x93, 0x83, 0x7d, 0x9b, 0x12, 0x5f, 0x59, 0x6f, 0xf4, 0xd2, 0x37, 0xfb, 0xaf, + 0x5f, 0xdb, 0xe2, 0xcd, 0xd9, 0x23, 0x4d, 0x62, 0x19, 0x27, 0x86, 0x93, 0xa0, 0x8d, 0x41, 0x38, + 0x18, 0x6e, 0x46, 0x5c, 0x6f, 0xc4, 0xba, 0x51, 0xd6, 0xe7, 0x9f, 0xeb, 0x47, 0x1c, 0x81, 0xa5, + 0xe8, 0xc8, 0x9e, 0xf0, 0xd2, 0x72, 0xfc, 0x07, 0xc3, 0xd2, 0x30, 0x17, 0x76, 0x37, 0x21, 0x2c, + 0xb3, 0x85, 0xf8, 0xc0, 0x8f, 0x2f, 0x6a, 0x80, 0xd1, 0x2f, 0x64, 0x3b, 0xa6, 0x6b, 0x73, 0x76, + 0xcc, 0x8f, 0x34, 0x6c, 0x11, 0xe2, 0x65, 0x63, 0x45, 0x48, 0x96, 0xed, 0xc5, 0x66, 0x0d, 0x65, + 0x90, 0x1b, 0x02, 0xd3, 0xd3, 0x2f, 0x67, 0x21, 0xb1, 0x8e, 0x94, 0x37, 0xcf, 0xcb, 0x9b, 0xdc, + 0x8b, 0x06, 0x2f, 0x33, 0xa5, 0x9c, 0x58, 0xa0, 0x8b, 0x6b, 0x07, 0x8b, 0x8a, 0x73, 0x12, 0xee, + 0x99, 0x3b, 0xca, 0xbb, 0x2a, 0x85, 0x55, 0x58, 0xee, 0x70, 0x37, 0x1d, 0xbe, 0x5e, 0xa9, 0xab, + 0xbb, 0xea, 0x5c, 0x40, 0x31, 0xbd, 0x9a, 0x94, 0x27, 0x49, 0x5b, 0xd2, 0xf5, 0x6e, 0x04, 0xb5, + 0xfd, 0x20, 0x48, 0xf1, 0xd6, 0x76, 0x64, 0x7f, 0xde, 0x7b, 0x67, 0xf7, 0xe9, 0xf3, 0x12, 0x4f, + 0xa9, 0x0b, 0xd7, 0x8b, 0x3b, 0x9d, 0xe5, 0x12, 0x96, 0x61, 0x3a, 0x39, 0xf2, 0x47, 0x49, 0x27, + 0xb0, 0x3f, 0x0a, 0xc0, 0x41, 0x0b, 0xf7, 0x04, 0x7a, 0xdd, 0x49, 0x20, 0x9f, 0xae, 0xdd, 0x87, + 0x8e, 0x62, 0x0e, 0x89, 0x84, 0x81, 0x3e, 0x29, 0x7a, 0x07, 0x2a, 0x80, 0xc2, 0xdc, 0x9a, 0xe1, + 0x2e, 0xec, 0x62, 0x34, 0x21, 0xde, 0xac, 0x35, 0xdb, 0xba, 0x70, 0x23, 0xe0, 0xbf, 0xea, 0x56, + 0xf3, 0x98, 0xdf, 0xca, 0xaa, 0x6e, 0xfa, 0x78, 0xb7, 0x49, 0x0b, 0x48, 0xb4, 0xb7, 0xe2, 0xfe, + 0x32, 0x31, 0x34, 0xa8, 0xc4, 0x74, 0x95, 0xe3, 0x54, 0xa1, 0xdc, 0xaf, 0xf6, 0xd0, 0x5f, 0x6b, + 0x8f, 0xde, 0x92, 0x74, 0x5c, 0x63, 0x75, 0xc7, 0xb5, 0xb2, 0x92, 0x72, 0x5b, 0x63, 0x9b, 0xb8, + 0xad, 0x29, 0xdf, 0xd1, 0xdb, 0x78, 0x6a, 0x21, 0x14, 0x23, 0x8e, 0xc0, 0xc2, 0x21, 0x58, 0x78, + 0x16, 0x72, 0x67, 0xfb, 0x66, 0x7f, 0x36, 0x83, 0x88, 0xe5, 0xad, 0xaa, 0xc7, 0xbe, 0xc9, 0x9f, + 0x6d, 0xe9, 0xd6, 0xc3, 0x51, 0x48, 0x75, 0xe3, 0x27, 0x9f, 0x80, 0x07, 0x95, 0x87, 0x1e, 0x66, + 0x03, 0xcc, 0xab, 0xf0, 0x5d, 0xb0, 0xd5, 0x42, 0x59, 0x84, 0x68, 0xeb, 0x0f, 0x5c, 0xbf, 0xe9, + 0xc3, 0x39, 0xe0, 0x11, 0x54, 0xd6, 0x4b, 0xc6, 0x4b, 0xf6, 0x8a, 0x81, 0x93, 0x94, 0xd7, 0xb2, + 0x70, 0xee, 0x1e, 0x91, 0xb3, 0x37, 0xa2, 0x65, 0x49, 0x77, 0x8c, 0x1a, 0x14, 0xc4, 0x5b, 0x8e, + 0x04, 0xc1, 0x23, 0x5b, 0xc7, 0x97, 0x2e, 0x31, 0x9c, 0x3f, 0xc8, 0x82, 0xc9, 0xee, 0x2b, 0x51, + 0xe2, 0x5e, 0x80, 0x94, 0xc9, 0x71, 0xea, 0x71, 0x61, 0x5a, 0x03, 0x68, 0xcc, 0x8b, 0x09, 0x0c, + 0x71, 0xac, 0xc3, 0x43, 0xe4, 0xcb, 0xea, 0x9a, 0xf0, 0xc8, 0x1d, 0x47, 0x5b, 0x08, 0x01, 0x45, + 0xd5, 0xf6, 0x44, 0x15, 0x49, 0x99, 0xde, 0x16, 0x84, 0x13, 0x85, 0x3e, 0x64, 0x05, 0xc1, 0x5f, + 0x68, 0x6e, 0x6b, 0x06, 0xa0, 0x95, 0x0e, 0x18, 0xa0, 0x85, 0x0b, 0x71, 0x31, 0xbc, 0xfe, 0xb8, + 0x8a, 0xac, 0x23, 0x9f, 0xd3, 0x10, 0x83, 0xc3, 0x91, 0x57, 0xec, 0xda, 0x8b, 0x46, 0x85, 0x9d, + 0xbf, 0xe6, 0x16, 0x85, 0x95, 0xa1, 0x82, 0x5b, 0x0b, 0xb0, 0x58, 0x55, 0x59, 0xcc, 0xc4, 0x4b, + 0x36, 0xa8, 0x5e, 0x6a, 0xc2, 0x75, 0xf3, 0xbe, 0xdf, 0x2f, 0x30, 0x60, 0xc3, 0x16, 0x8e, 0xe4, + 0x2e, 0x79, 0xcb, 0x24, 0xa0, 0xbf, 0xe6, 0x01, 0xc6, 0x5f, 0x00, 0xa7, 0xcf, 0x6d, 0xcc, 0xa2, + 0x92, 0x50, 0x28, 0xad, 0xdd, 0xa3, 0x9b, 0x1b, 0xd7, 0xe3, 0xe4, 0xda, 0xfa, 0xc4, 0xcb, 0x79, + 0x26, 0xe7, 0x13, 0x6f, 0x30, 0x40, 0xaa, 0x2e, 0x67, 0x14, 0x6e, 0xb2, 0x00, 0xd9, 0x44, 0x35, + 0x18, 0x08, 0xbe, 0x17, 0x07, 0x3d, 0xe9, 0x91, 0x43, 0x42, 0xd1, 0x84, 0xc1, 0xe6, 0x4a, 0xf6, + 0x9c, 0xb8, 0x13, 0xc2, 0x69, 0xb1, 0xc5, 0x43, 0x82, 0x53, 0x94, 0xfb, 0xe2, 0x61, 0xef, 0xe9, + 0x29, 0x1c, 0xfa, 0x2e, 0x77, 0xb0, 0xbe, 0xb0, 0x1c, 0x14, 0x5e, 0xac, 0xbb, 0x68, 0x5e, 0xdc, + 0x86, 0xb1, 0x0b, 0xe2, 0x4b, 0xeb, 0x83, 0x61, 0xc9, 0x8c, 0x70, 0x84, 0x3a, 0x01, 0xa3, 0x03, + 0x55, 0x83, 0x43, 0xbc, 0xb9, 0x37, 0xc8, 0x3b, 0xd0, 0xd5, 0x02, 0x35, 0x10, 0xd5, 0xdc, 0x2d, + 0x34, 0x92, 0xe2, 0xf8, 0x86, 0x4e, 0xe9, 0xe3, 0x50, 0xad, 0xbd, 0xaa, 0x77, 0xf4, 0x2e, 0x75, + 0xd7, 0x75, 0x87, 0x62, 0xa2, 0x0e, 0x06, 0x86, 0x6e, 0x6e, 0x2d, 0x91, 0x5a, 0x18, 0x8b, 0x61, + 0xd4, 0x5d, 0xa6, 0x17, 0xee, 0x20, 0x41, 0x40, 0x39, 0x79, 0x1f, 0x6d, 0xfc, 0x2c, 0x1c, 0xcf, + 0x12, 0x6d, 0x00, 0xe1, 0x74, 0xaa, 0x3f, 0xdd, 0x73, 0x36, 0x7a, 0xcc, 0xd7, 0x78, 0x7c, 0x81, + 0x40, 0xbf, 0x2b, 0x7a, 0xaa, 0xbd, 0x00, 0x44, 0x71, 0x69, 0x08, 0x90, 0xe2, 0x27, 0xf1, 0x09, + 0x5e, 0x24, 0x1b, 0x61, 0x52, 0x88, 0xf8, 0x46, 0x97, 0xca, 0x18, 0x8a, 0x87, 0x61, 0x42, 0x6d, + 0x4d, 0x3f, 0xc7, 0x46, 0xbb, 0x24, 0x7e, 0xb7, 0x51, 0xb3, 0xd3, 0xc7, 0x64, 0x62, 0x34, 0x94, + 0xb0, 0x71, 0x46, 0xeb, 0x95, 0xb6, 0x3c, 0xc1, 0x4a, 0xdf, 0xcf, 0x23, 0x78, 0xab, 0x1b, 0x55, + 0x3d, 0xcd, 0x18, 0xae, 0xcd, 0x66, 0x95, 0x61, 0xa7, 0xb1, 0x24, 0x8f, 0x8a, 0x47, 0x6c, 0x50, + 0xaf, 0x74, 0x8f, 0x17, 0xe3, 0x35, 0x4b, 0x97, 0xc4, 0xfe, 0xa9, 0x40, 0xd4, 0x34, 0xb5, 0x1f, + 0x87, 0x71, 0x53, 0xf3, 0xe3, 0xe3, 0x2f, 0x95, 0xc6, 0x1a, 0x82, 0x8f, 0x06, 0x21, 0x33, 0xa8, + 0x77, 0x79, 0xc5, 0x1e, 0xee, 0x6b, 0x5d, 0x62, 0x76, 0x95, 0x87, 0xf5, 0xc3, 0xc9, 0xda, 0xa6, + 0x43, 0x78, 0x4d, 0x70, 0xb2, 0x11, 0xc0, 0xd7, 0xfa, 0x7e, 0xae, 0x57, 0xf6, 0xb3, 0xc5, 0x27, + 0xf8, 0x9c, 0x0e, 0x41, 0x9d, 0x5a, 0x33, 0x34, 0xa8, 0xb1, 0xbe, 0x9b, 0xaf, 0x93, 0x7c, 0xf7, + 0x7e, 0x45, 0x27, 0x04, 0x20, 0xb7, 0xc1, 0x32, 0x85, 0xf1, 0x8a, 0xd1, 0x54, 0xdb, 0x6b, 0xbe, + 0xa3, 0x66, 0x10, 0xb5, 0xee, 0x3f, 0x2a, 0xba, 0xf8, 0x05, 0x03, 0x49, 0x24, 0xc6, 0x58, 0x6d, + 0x11, 0xf0, 0x47, 0xf5, 0x50, 0xca, 0x79, 0x05, 0x6c, 0xef, 0x06, 0x9d, 0x47, 0xc9, 0x19, 0xc9, + 0x27, 0x05, 0x3f, 0x9d, 0xe3, 0x4c, 0x84, 0x5b, 0x12, 0x48, 0xc6, 0x36, 0x55, 0x44, 0x51, 0xed, + 0xef, 0x8e, 0x9f, 0x3d, 0x60, 0x54, 0x8a, 0x56, 0x82, 0x89, 0x43, 0x5c, 0x5b, 0xe8, 0xf7, 0xd8, + 0x2c, 0x1b, 0xd1, 0xbf, 0x18, 0x41, 0x98, 0x48, 0x97, 0xff, 0x7d, 0x82, 0x1b, 0xb5, 0x2d, 0x84, + 0x59, 0xb6, 0xec, 0x4e, 0xd2, 0xb4, 0x13, 0x62, 0xd3, 0x29, 0xef, 0x82, 0xc0, 0x08, 0x22, 0xb4, + 0xf9, 0xc3, 0x59, 0x43, 0xf1, 0x33, 0x38, 0x24, 0xf4, 0xd2, 0xe9, 0xc6, 0x37, 0x4b, 0x52, 0x37, + 0x51, 0x62, 0x6a, 0x92, 0x8f, 0x69, 0x9e, 0x78, 0xb1, 0xde, 0x40, 0xc6, 0x7c, 0xa6, 0x34, 0xcd, + 0xca, 0x3a, 0x6b, 0x91, 0x57, 0x42, 0x1b, 0xd2, 0x81, 0xd6, 0xb8, 0x64, 0x7e, 0xc4, 0x15, 0xa4, + 0xd1, 0x6e, 0xaf, 0xdf, 0xd3, 0xea, 0xdf, 0x64, 0x27, 0x25, 0x72, 0x39, 0xa6, 0x7f, 0xef, 0x26, + 0x32, 0x12, 0x85, 0x81, 0xc4, 0x34, 0x44, 0x33, 0x89, 0x00, 0x67, 0x44, 0xa3, 0x0f, 0xfe, 0x8e, + 0x48, 0xb4, 0xf3, 0x63, 0x04, 0x74, 0xa4, 0x18, 0x7c, 0x34, 0x7a, 0xd4, 0x8b, 0x07, 0x0f, 0x41, + 0x32, 0x7c, 0xf5, 0x3d, 0xb0, 0xe8, 0xa3, 0xc3, 0x37, 0x4f, 0x4f, 0xc9, 0xf0, 0xf5, 0x2b, 0xfc, + 0xfc, 0xb6, 0x87, 0x9f, 0xdf, 0xbe, 0xc6, 0xcf, 0xbd, 0xfd, 0x03, 0xfc, 0x02, 0x52, 0xea, 0xc8, + 0x0e, 0x60, 0x2a, 0x43, 0xdb, 0x7b, 0x0c, 0x72, 0x6a, 0x94, 0x53, 0xa3, 0x9c, 0x1a, 0xe5, 0xd4, + 0x28, 0xa7, 0x46, 0x39, 0x6f, 0x94, 0xeb, 0x8d, 0x64, 0xf6, 0x27, 0x4a, 0x5e, 0xaf, 0x42, 0xec, + 0x46, 0xf6, 0x91, 0xdd, 0x7f, 0x70, 0x3b, 0x62, 0x4a, 0x15, 0x65, 0x91, 0x4c, 0x3c, 0x66, 0xdd, + 0x47, 0xb7, 0xc3, 0xe7, 0xe1, 0x89, 0xa4, 0x63, 0x0b, 0xca, 0xe6, 0x10, 0x4d, 0x08, 0xa8, 0x54, + 0xb7, 0xaa, 0x69, 0x71, 0xbb, 0x8b, 0xfb, 0x7c, 0x7b, 0xfb, 0x3e, 0x07, 0x2e, 0x9e, 0xe6, 0x18, + 0x79, 0x7f, 0xcf, 0x33, 0x8e, 0x2b, 0x18, 0xb1, 0xed, 0x6d, 0x05, 0x28, 0x76, 0x9f, 0x0f, 0x77, + 0x7b, 0x46, 0x68, 0xca, 0x4d, 0x78, 0xcd, 0x7e, 0x3b, 0xf5, 0x0e, 0x8d, 0x38, 0x61, 0x5e, 0x48, + 0x3d, 0x3f, 0x3d, 0x39, 0xce, 0x3d, 0x47, 0x6c, 0xf9, 0x8d, 0x8d, 0x4f, 0xe1, 0x7d, 0x33, 0x50, + 0x81, 0x05, 0x60, 0xe1, 0x0a, 0x8c, 0x8d, 0x91, 0x7d, 0x9f, 0x83, 0x70, 0x06, 0xff, 0x62, 0x48, + 0x2c, 0x61, 0x4c, 0x10, 0xe4, 0x04, 0x07, 0x9a, 0xa8, 0xb6, 0xba, 0x4a, 0xf3, 0x82, 0x34, 0xb7, + 0x8e, 0xbd, 0x87, 0x2d, 0x5c, 0x4c, 0xaa, 0x16, 0xce, 0x1f, 0xbf, 0x90, 0x19, 0x83, 0x60, 0x21, + 0xc6, 0xb7, 0xb3, 0x19, 0x62, 0x1b, 0xde, 0x63, 0x62, 0xdf, 0x1b, 0x96, 0xe7, 0xa1, 0x9e, 0x2b, + 0x90, 0x49, 0xd4, 0x7b, 0x32, 0x07, 0xea, 0xd0, 0xf7, 0x64, 0xed, 0x7a, 0x4f, 0x8d, 0x95, 0x10, + 0xab, 0x23, 0x67, 0x50, 0x03, 0x77, 0x60, 0xc4, 0x6e, 0x6b, 0x21, 0x3a, 0xae, 0xa7, 0x7d, 0xe1, + 0xeb, 0x5a, 0x8b, 0x78, 0xf6, 0xbe, 0x25, 0xfa, 0xdc, 0xff, 0xfe, 0xad, 0xdc, 0xf6, 0xa8, 0x0c, + 0xcc, 0xd2, 0x41, 0xeb, 0xd9, 0x4e, 0xba, 0x67, 0x75, 0x2b, 0x26, 0xa8, 0x78, 0xa3, 0xaf, 0xb9, + 0x58, 0x4b, 0xda, 0xfa, 0x04, 0x6c, 0x3c, 0xfc, 0xfe, 0x7b, 0xbf, 0xca, 0x03, 0x15, 0xa4, 0x68, + 0x22, 0xe0, 0x62, 0xb7, 0xb7, 0x0d, 0x8f, 0xf9, 0x84, 0xb6, 0x10, 0xcf, 0x93, 0xeb, 0x19, 0xbc, + 0x30, 0xf1, 0x72, 0x04, 0xb4, 0x08, 0xa7, 0x22, 0x71, 0x25, 0xaf, 0x83, 0xf0, 0xbe, 0xf4, 0x26, + 0x88, 0xe8, 0x02, 0x54, 0x48, 0x05, 0x15, 0x62, 0xd5, 0x47, 0xaa, 0x1b, 0x04, 0x81, 0x22, 0x99, + 0xee, 0xcf, 0x27, 0x1f, 0x3e, 0x03, 0x2d, 0x7d, 0x6b, 0x9c, 0xbe, 0x24, 0x6a, 0x57, 0x3c, 0x16, + 0xb1, 0x08, 0xe8, 0xa9, 0x73, 0xf6, 0x35, 0xff, 0xc4, 0x2e, 0xc3, 0x38, 0xa8, 0xee, 0x10, 0x39, + 0x62, 0x09, 0x13, 0x40, 0xe8, 0xb9, 0x98, 0x6c, 0xd2, 0x5b, 0x29, 0xc3, 0x88, 0x00, 0x3a, 0x86, + 0x6e, 0x6d, 0x84, 0xb4, 0x0b, 0x9c, 0x8b, 0x3c, 0x85, 0x39, 0x96, 0x2d, 0x7d, 0x9b, 0xde, 0xce, + 0x05, 0xa2, 0x2d, 0x7d, 0x2d, 0x78, 0x55, 0x02, 0x9a, 0xa5, 0x82, 0x1b, 0xf8, 0x54, 0x22, 0xf0, + 0xb2, 0xee, 0xed, 0x34, 0x4b, 0x40, 0xa7, 0x49, 0xa6, 0x15, 0x84, 0x5b, 0x38, 0x7f, 0x73, 0x04, + 0xa1, 0x15, 0x87, 0x09, 0x25, 0xa9, 0x6a, 0x18, 0x5d, 0x51, 0xa8, 0x61, 0x15, 0xf3, 0xbd, 0x9e, + 0xef, 0x35, 0xb8, 0x59, 0x0b, 0x52, 0xf2, 0x3d, 0xf4, 0x96, 0x16, 0xa8, 0xbe, 0x0a, 0xe8, 0x57, + 0x61, 0xfb, 0x56, 0x6c, 0xea, 0x0d, 0x3e, 0xd5, 0x68, 0x5d, 0xe7, 0x49, 0xc5, 0x72, 0x3c, 0xc6, + 0xd0, 0x95, 0xba, 0x40, 0xe3, 0xbf, 0xe6, 0xdb, 0x4c, 0x87, 0x11, 0xe6, 0x51, 0xec, 0xf1, 0x98, + 0x59, 0xaa, 0x01, 0x32, 0xaf, 0xbb, 0xc8, 0x8f, 0xf8, 0x49, 0x45, 0x5a, 0xdb, 0xbe, 0xf0, 0xc7, + 0x36, 0xd0, 0x0c, 0x49, 0xb3, 0x9b, 0x9c, 0x69, 0x1d, 0x4b, 0x38, 0xca, 0xa7, 0xa0, 0xb7, 0x1d, + 0x2b, 0x98, 0xc8, 0xa7, 0x60, 0x9f, 0x7f, 0x83, 0x71, 0x3f, 0x05, 0xaf, 0xb6, 0xe3, 0x25, 0xb7, + 0xa6, 0xf1, 0x86, 0x09, 0x59, 0xcb, 0x30, 0x94, 0xde, 0x5d, 0x34, 0x3f, 0x20, 0xd4, 0x7a, 0x36, + 0x3a, 0xd6, 0xfa, 0xa5, 0x6e, 0x91, 0x6b, 0x84, 0xf2, 0xb0, 0xd1, 0x2c, 0x32, 0x9f, 0x53, 0x4b, + 0xae, 0xae, 0x30, 0xc2, 0x10, 0x47, 0xd6, 0xb0, 0x18, 0x05, 0x2e, 0x72, 0x88, 0x92, 0x60, 0xc5, + 0x2c, 0x7f, 0x40, 0xf6, 0x78, 0xfa, 0x05, 0x03, 0x93, 0xc5, 0x29, 0x7e, 0x00, 0x6b, 0xa3, 0x40, + 0x38, 0x4b, 0x3d, 0x52, 0x56, 0x43, 0xf0, 0x29, 0x02, 0x6c, 0xe1, 0xc8, 0x9e, 0x03, 0x0d, 0x64, + 0x06, 0xe1, 0x91, 0xa5, 0x5a, 0x0b, 0xd5, 0x27, 0xc5, 0xf6, 0x36, 0xfd, 0x19, 0x06, 0x7e, 0xf3, + 0xad, 0x31, 0x91, 0xf3, 0x3b, 0x45, 0x35, 0x54, 0xd9, 0xf5, 0xd6, 0x08, 0xe4, 0xaa, 0x72, 0xfe, + 0xe0, 0x6d, 0x20, 0x8e, 0xab, 0xea, 0xd1, 0x83, 0xd0, 0x6c, 0xc5, 0xb5, 0xa2, 0x69, 0x92, 0xb8, + 0xe0, 0x88, 0x2c, 0xdc, 0xec, 0x3c, 0x7b, 0xb0, 0xcf, 0xcf, 0x94, 0x39, 0x3a, 0xec, 0xce, 0x1e, + 0x96, 0xf6, 0x39, 0x68, 0x6b, 0xe9, 0x28, 0xed, 0x0a, 0xa7, 0x16, 0xd8, 0xc7, 0x7d, 0x75, 0x2e, + 0xcc, 0x59, 0x4c, 0x98, 0x4e, 0x22, 0x75, 0x6f, 0xe3, 0x03, 0xec, 0xae, 0xae, 0x37, 0x62, 0xf6, + 0xd1, 0x95, 0x9a, 0x25, 0xcf, 0x71, 0xd4, 0x3c, 0x54, 0xba, 0x29, 0x3a, 0xd3, 0x2c, 0xf1, 0xe5, + 0x08, 0x81, 0x4e, 0xe6, 0x2b, 0x34, 0x46, 0x0d, 0xcd, 0x1a, 0xdb, 0x48, 0x50, 0xe6, 0x55, 0x6b, + 0x21, 0xef, 0xea, 0x8c, 0x05, 0x81, 0x42, 0x7a, 0x9e, 0xb6, 0x1a, 0x9e, 0x13, 0x05, 0xcd, 0xdd, + 0xe9, 0x33, 0x77, 0xd7, 0x4d, 0xbc, 0x6d, 0x48, 0x0d, 0x73, 0x2e, 0x07, 0xd1, 0x3a, 0xdf, 0xe2, + 0xe9, 0x69, 0xde, 0xcd, 0x27, 0xf3, 0x34, 0x8e, 0x81, 0x24, 0xd2, 0xbf, 0x46, 0xec, 0xde, 0x59, + 0x8c, 0xd9, 0x55, 0x78, 0x17, 0x61, 0xae, 0x51, 0x89, 0xf7, 0x42, 0x87, 0x10, 0x9c, 0x41, 0x70, + 0x60, 0xb2, 0x1c, 0x6d, 0xa0, 0xc0, 0x30, 0x08, 0xcd, 0x83, 0x02, 0xd5, 0xc4, 0x67, 0x7e, 0x2a, + 0x4f, 0x30, 0x44, 0x44, 0xde, 0x0b, 0xc8, 0x1f, 0xe8, 0x1e, 0xa0, 0xe7, 0xf7, 0xe1, 0xc7, 0xd2, + 0x44, 0x4a, 0x89, 0x84, 0xad, 0x99, 0x8a, 0x95, 0xd9, 0xb2, 0x07, 0x63, 0x60, 0xf7, 0xd7, 0xfc, + 0xd6, 0xa0, 0xd7, 0xc3, 0xda, 0x18, 0xc1, 0xc5, 0x92, 0xf4, 0xf6, 0xf2, 0xca, 0xca, 0xb3, 0x70, + 0xc2, 0x30, 0xf8, 0x3a, 0xc7, 0xd0, 0x77, 0x1e, 0x58, 0x56, 0x69, 0xb2, 0x8f, 0x4d, 0x04, 0x00, + 0x03, 0x3e, 0x41, 0x98, 0xa3, 0x8d, 0x3a, 0x6f, 0xb1, 0xce, 0x3b, 0xed, 0xb1, 0x16, 0x8d, 0xd1, + 0x02, 0x5e, 0x62, 0xa5, 0x13, 0xe0, 0xeb, 0x68, 0xe6, 0x5e, 0x6a, 0x1c, 0x84, 0x24, 0x04, 0x0a, + 0xb2, 0xa1, 0x8a, 0x20, 0x04, 0xc1, 0x97, 0x09, 0xc5, 0xb8, 0x95, 0x5c, 0x84, 0xd8, 0x9a, 0x76, + 0x86, 0xf5, 0x06, 0xda, 0x09, 0x56, 0x62, 0x34, 0xd1, 0x19, 0xe6, 0x37, 0x1b, 0xe9, 0x36, 0x38, + 0x41, 0xc3, 0xb7, 0x3e, 0xf0, 0x30, 0x06, 0xef, 0x4c, 0x3e, 0x09, 0xf8, 0x66, 0x09, 0xad, 0xae, + 0xe0, 0xf8, 0x34, 0x51, 0x07, 0xce, 0x6a, 0x5d, 0xf0, 0xd1, 0x24, 0x46, 0x33, 0xda, 0xda, 0x3b, + 0x60, 0x07, 0xea, 0x7e, 0x92, 0x67, 0x6e, 0x45, 0x60, 0x1c, 0x6e, 0x10, 0xcf, 0x23, 0xbb, 0xcf, + 0xd4, 0x17, 0x3c, 0x8d, 0x41, 0x20, 0xd9, 0xd3, 0x63, 0xc0, 0xf3, 0x9a, 0x29, 0x1d, 0xcd, 0xfc, + 0x17, 0xae, 0x38, 0x3b, 0x30, 0xb8, 0x1c, 0xb3, 0x04, 0xad, 0x97, 0x2d, 0xbc, 0x10, 0xd1, 0x07, + 0x32, 0x90, 0x23, 0x6d, 0x6e, 0x6f, 0xa7, 0xc0, 0x3a, 0xb4, 0x99, 0x12, 0xe6, 0x3d, 0x9c, 0xb0, + 0x30, 0x7c, 0x1d, 0xdb, 0x05, 0x27, 0xdc, 0x4d, 0xd2, 0x7b, 0xc7, 0xc5, 0xa8, 0x40, 0x69, 0x9c, + 0x6b, 0x3e, 0x7c, 0xf9, 0xdd, 0x0c, 0x6c, 0xd3, 0x68, 0x0a, 0xac, 0x8f, 0x7f, 0x90, 0x09, 0x83, + 0x78, 0x52, 0x00, 0x75, 0x7c, 0xcb, 0x24, 0x69, 0x8a, 0xbd, 0xf7, 0xfc, 0x97, 0xa9, 0x34, 0x06, + 0x46, 0x5b, 0x70, 0x80, 0x93, 0x2f, 0x53, 0xd9, 0x20, 0x88, 0xdc, 0xa5, 0x23, 0x62, 0x92, 0xcb, + 0x08, 0x4b, 0xa6, 0xa1, 0x3d, 0x20, 0x2a, 0xb6, 0x13, 0x53, 0x9a, 0xf4, 0x78, 0x74, 0x9f, 0x93, + 0x20, 0xe1, 0x24, 0x4f, 0x4f, 0x3b, 0x0b, 0xfb, 0xce, 0xee, 0x23, 0x7e, 0xd6, 0x72, 0xc7, 0xed, + 0xf3, 0x4b, 0x87, 0x5c, 0x5d, 0x3a, 0x84, 0x1e, 0xa2, 0xf7, 0x31, 0xc4, 0x69, 0x47, 0x34, 0x1f, + 0x60, 0xdb, 0xc5, 0x2e, 0x5e, 0xee, 0xc0, 0xf2, 0x20, 0x36, 0x5c, 0xc4, 0x39, 0x2c, 0xbd, 0x8c, + 0x81, 0x85, 0x5a, 0x13, 0x0a, 0x24, 0xbf, 0x7e, 0xf9, 0x71, 0xf7, 0x8d, 0xbd, 0xf4, 0x30, 0xe9, + 0x4e, 0x3f, 0xf9, 0x86, 0x48, 0xfb, 0x04, 0x44, 0x32, 0xb4, 0xdd, 0xfd, 0xe7, 0xa5, 0x69, 0xa0, + 0xf0, 0xc4, 0xb8, 0x53, 0xf9, 0x70, 0x93, 0x15, 0x8f, 0xb0, 0x8b, 0xf2, 0x0c, 0x14, 0x47, 0xc6, + 0xcf, 0xf0, 0x2d, 0x10, 0xc3, 0x6e, 0x27, 0x13, 0xd0, 0x1e, 0x5c, 0x81, 0xd5, 0x98, 0x10, 0x67, + 0x97, 0xdb, 0x62, 0x7b, 0x7b, 0x0b, 0x49, 0xcd, 0x84, 0x45, 0xda, 0x22, 0x22, 0x5c, 0xe0, 0x96, + 0xa8, 0xba, 0x47, 0x25, 0x5d, 0xee, 0xdc, 0x99, 0x97, 0x61, 0xa9, 0xca, 0x6f, 0x26, 0xe9, 0x4a, + 0xe7, 0x80, 0x56, 0x18, 0x36, 0x1d, 0x99, 0xa1, 0x56, 0xae, 0x79, 0x16, 0x18, 0x3f, 0xd2, 0x08, + 0x93, 0xae, 0xae, 0xcf, 0x49, 0x7d, 0x4d, 0xa6, 0x51, 0xf2, 0xf6, 0x0f, 0x51, 0x66, 0x2f, 0x25, + 0x63, 0xb9, 0x9f, 0x78, 0x33, 0xd8, 0x2b, 0x31, 0xe9, 0xe1, 0x0d, 0x2a, 0x07, 0x16, 0xd3, 0x75, + 0xaf, 0x61, 0x4d, 0x08, 0x3d, 0xfb, 0x07, 0xfa, 0x36, 0x46, 0x6d, 0x22, 0xa0, 0x44, 0xbf, 0x4e, + 0x1b, 0x08, 0x7b, 0x69, 0x18, 0xb1, 0x41, 0x19, 0x44, 0x11, 0x9c, 0x03, 0x79, 0x23, 0x18, 0x69, + 0x2c, 0x0c, 0x08, 0x4e, 0x18, 0xd8, 0xce, 0x27, 0xf8, 0xe8, 0x62, 0x04, 0x15, 0xe1, 0x25, 0xca, + 0x42, 0x51, 0x32, 0xed, 0x72, 0xd7, 0xcd, 0xb0, 0x4c, 0xf6, 0x10, 0x73, 0x09, 0x6f, 0x82, 0x5f, + 0x8c, 0xa4, 0x16, 0x18, 0x06, 0x30, 0x97, 0xa9, 0x1f, 0x44, 0x2d, 0xf8, 0x06, 0xa2, 0x20, 0xe5, + 0x58, 0x88, 0x31, 0x34, 0x12, 0x61, 0x2b, 0xb6, 0x78, 0xca, 0x3a, 0xed, 0x6d, 0x68, 0x6f, 0xc0, + 0xe3, 0x28, 0x2f, 0xcf, 0xd1, 0xc1, 0xe2, 0x6f, 0xd3, 0xc1, 0xe2, 0x66, 0x1d, 0x0c, 0x44, 0x3c, + 0x43, 0xf3, 0x42, 0x56, 0x9a, 0x48, 0x75, 0xab, 0xd4, 0x67, 0x08, 0xff, 0xfc, 0x5b, 0x2f, 0xf9, + 0xaa, 0x97, 0x4b, 0x64, 0x72, 0xae, 0x00, 0x02, 0x2e, 0xd2, 0xa4, 0xcf, 0x53, 0x8d, 0xe0, 0xbf, + 0xcb, 0xda, 0x85, 0xd4, 0xe7, 0xd8, 0xd1, 0x1f, 0xe5, 0x50, 0x3a, 0x12, 0xf8, 0xc7, 0x1d, 0x5d, + 0xe0, 0xb2, 0xce, 0x05, 0x74, 0x5c, 0xd7, 0xfa, 0x3b, 0x42, 0xc6, 0x70, 0xec, 0x30, 0x82, 0x6c, + 0x21, 0x19, 0xfb, 0xc5, 0x82, 0x34, 0xa7, 0x21, 0x2c, 0x0f, 0x1c, 0x03, 0x7d, 0x0a, 0xde, 0x5a, + 0x52, 0x29, 0xea, 0x4f, 0x50, 0x78, 0x47, 0x29, 0x94, 0xc2, 0x59, 0x01, 0x7f, 0xf9, 0x0f, 0xa0, + 0x77, 0x2d, 0xd1, 0xa9, 0x9e, 0x6e, 0x16, 0x2f, 0xfa, 0x36, 0x7f, 0xca, 0x94, 0xd1, 0x73, 0x30, + 0x40, 0x17, 0x6f, 0x8e, 0x8d, 0x19, 0x24, 0x71, 0x1f, 0x67, 0x01, 0x83, 0x5a, 0xd6, 0xc7, 0x8f, + 0x46, 0x6f, 0x73, 0x06, 0x65, 0xb2, 0x14, 0xf9, 0xc9, 0x1d, 0xd9, 0x3f, 0xd3, 0x1d, 0x35, 0x0d, + 0x9f, 0x70, 0x28, 0x11, 0x36, 0x26, 0x61, 0xc5, 0x7d, 0x3a, 0xbf, 0xe6, 0xd3, 0x81, 0xc3, 0xc3, + 0xc2, 0xfa, 0x28, 0x5b, 0x10, 0x10, 0x0d, 0xbc, 0xe5, 0x2e, 0x0c, 0xfd, 0x0b, 0x7e, 0xe6, 0xd3, + 0x26, 0x68, 0x9a, 0xf5, 0xfd, 0x58, 0x71, 0x9a, 0x5c, 0x42, 0x25, 0xec, 0xad, 0x6b, 0x4b, 0x5c, + 0xd0, 0x05, 0xaa, 0x8d, 0xfd, 0x05, 0xb2, 0xfb, 0xbe, 0x1c, 0xd7, 0x72, 0x39, 0xd0, 0x08, 0x9f, + 0x8e, 0x12, 0x52, 0x2e, 0xe7, 0xb8, 0x0d, 0xd4, 0xe0, 0x8d, 0xc5, 0xa8, 0xdf, 0x28, 0xe2, 0xbe, + 0xbb, 0x43, 0x89, 0x0d, 0x95, 0x62, 0xcc, 0x21, 0x83, 0xff, 0x36, 0x99, 0x14, 0x45, 0xbd, 0x1a, + 0xdd, 0x63, 0xfd, 0x66, 0xf3, 0x38, 0x9a, 0x78, 0x6a, 0x97, 0xe3, 0xb2, 0x1f, 0x78, 0x75, 0xea, + 0xb3, 0x3d, 0x58, 0xfd, 0xbc, 0xf9, 0x84, 0x3f, 0x85, 0x01, 0x29, 0x8c, 0x31, 0x0b, 0xc5, 0x38, + 0x0e, 0x93, 0xeb, 0x15, 0x37, 0x19, 0xd5, 0x8b, 0x13, 0x1c, 0xa1, 0x76, 0x87, 0x41, 0x73, 0xdc, + 0x44, 0x90, 0xa0, 0xdf, 0xe9, 0x88, 0x85, 0xf3, 0x35, 0x86, 0x03, 0x76, 0x16, 0xc6, 0x39, 0x9e, + 0xb0, 0x02, 0xd8, 0xb2, 0xba, 0x98, 0xb4, 0x75, 0x71, 0x21, 0x89, 0x45, 0x48, 0xac, 0x4a, 0xe1, + 0x44, 0x00, 0xb2, 0xbb, 0x48, 0xce, 0xc3, 0xff, 0xba, 0xd5, 0x2d, 0x2f, 0x58, 0x42, 0x23, 0x4a, + 0x33, 0x94, 0x37, 0x18, 0x9b, 0x79, 0x47, 0x1b, 0x59, 0x95, 0x5b, 0x17, 0xeb, 0x63, 0x65, 0xad, + 0x78, 0x8f, 0xe5, 0x6a, 0xd5, 0x76, 0xbc, 0x70, 0x89, 0x90, 0x6c, 0x4b, 0x9f, 0x79, 0x99, 0x74, + 0x48, 0x7c, 0x68, 0x78, 0x6a, 0x62, 0x70, 0x4f, 0x7d, 0x2e, 0xd4, 0xe2, 0xf7, 0x4d, 0x46, 0xb2, + 0x66, 0x7d, 0x42, 0xa2, 0x5b, 0xed, 0xfd, 0xab, 0xf7, 0xa3, 0xb9, 0x78, 0x98, 0x06, 0x4f, 0x0c, + 0x07, 0x90, 0x97, 0xa3, 0xe4, 0xfe, 0x63, 0x78, 0xe5, 0xfb, 0x9b, 0x5e, 0x80, 0xeb, 0xad, 0x76, + 0x7b, 0xe5, 0xf5, 0xb6, 0xd7, 0xf3, 0xdd, 0xce, 0xf3, 0xfd, 0xcb, 0xcd, 0xde, 0x72, 0xbd, 0xb7, + 0x3e, 0x1c, 0xeb, 0x85, 0xee, 0x3e, 0xce, 0x02, 0x91, 0x08, 0x36, 0xa9, 0x85, 0xa7, 0x73, 0xdf, + 0xb1, 0x7a, 0x1c, 0x34, 0x26, 0x49, 0x94, 0x3f, 0x5b, 0x9f, 0x41, 0x0f, 0x10, 0xb1, 0x02, 0x96, + 0xf9, 0xe8, 0x25, 0x77, 0x2d, 0x6b, 0x0b, 0x6d, 0x56, 0xb1, 0xbe, 0x4d, 0x41, 0xcb, 0x46, 0x3f, + 0xf5, 0xf8, 0x65, 0x15, 0x39, 0x21, 0x80, 0xb5, 0xea, 0x81, 0xc5, 0xaa, 0xf7, 0xba, 0x83, 0xdb, + 0xc6, 0x51, 0xc3, 0xc6, 0x20, 0x36, 0x0f, 0x20, 0xde, 0x20, 0x66, 0xb8, 0x3d, 0x32, 0xaf, 0x8c, + 0x87, 0xdb, 0x2c, 0x56, 0xf5, 0x5f, 0x10, 0xa9, 0xba, 0x36, 0x62, 0xaf, 0x0c, 0xe5, 0x5a, 0x35, + 0xba, 0x8d, 0x42, 0xdf, 0x8c, 0x25, 0xfd, 0xb6, 0x58, 0x55, 0xd6, 0x16, 0x09, 0x57, 0xa1, 0x99, + 0x96, 0xa0, 0xb8, 0x2a, 0x65, 0xad, 0x5c, 0x88, 0xe7, 0xcf, 0xe9, 0xdb, 0x83, 0x58, 0x59, 0x35, + 0x7c, 0x75, 0x5d, 0xcd, 0xff, 0xdc, 0x22, 0x6c, 0x12, 0x22, 0x68, 0x74, 0xd9, 0x14, 0x2d, 0x68, + 0x3e, 0x73, 0x60, 0x69, 0xc1, 0x42, 0x83, 0xb6, 0x30, 0x42, 0x45, 0x79, 0x7a, 0x08, 0x61, 0x5b, + 0x00, 0xa1, 0xf1, 0x00, 0x8a, 0x25, 0xd4, 0x16, 0x91, 0x2d, 0x91, 0xee, 0xf5, 0x02, 0x8c, 0x68, + 0xc9, 0xe9, 0xe0, 0x2a, 0xb9, 0x89, 0xf8, 0x20, 0x7c, 0x50, 0x07, 0x9b, 0x05, 0x53, 0xe9, 0x49, + 0xd1, 0xcb, 0x39, 0x2d, 0x36, 0x8d, 0xc4, 0x6a, 0x46, 0x04, 0x14, 0xe0, 0x55, 0xda, 0x42, 0xaa, + 0xe3, 0xa6, 0xfe, 0x46, 0x24, 0xce, 0x15, 0x5f, 0xc6, 0xde, 0x9b, 0x90, 0x96, 0xf1, 0xdd, 0x74, + 0x2a, 0x39, 0xb3, 0x72, 0xdb, 0x44, 0x7e, 0xb9, 0x43, 0xec, 0x3e, 0x8b, 0x79, 0x8e, 0x39, 0xbf, + 0xbf, 0xc8, 0xf2, 0x3e, 0xba, 0x4f, 0x4f, 0x6f, 0xe7, 0xfd, 0x33, 0xf4, 0x73, 0xf1, 0x4a, 0x43, + 0x43, 0xff, 0x6c, 0xb7, 0x77, 0x0e, 0x92, 0x21, 0xc6, 0xe7, 0xf5, 0x7d, 0x6f, 0x8e, 0xb9, 0xc0, + 0x50, 0xb4, 0xf4, 0x41, 0xa6, 0x34, 0x0e, 0xc2, 0x13, 0xe0, 0xdc, 0xb1, 0xc3, 0xe8, 0xd2, 0x44, + 0x81, 0xb7, 0x54, 0x81, 0xd0, 0x14, 0xa8, 0x5c, 0x33, 0x0e, 0x9a, 0xc7, 0x41, 0x5b, 0x12, 0x03, + 0xaa, 0x45, 0xe8, 0xa0, 0x67, 0xf9, 0x39, 0xdd, 0x71, 0x8e, 0xd4, 0xa7, 0x7e, 0x89, 0x7e, 0x4a, + 0x65, 0xd0, 0x3f, 0xaa, 0x6a, 0xf2, 0xf7, 0x12, 0xc4, 0xb4, 0x5a, 0xd2, 0xcd, 0xf0, 0x82, 0x92, + 0xe0, 0x58, 0x78, 0x32, 0x32, 0xb1, 0xe7, 0x10, 0x8d, 0x8e, 0xfa, 0x59, 0x22, 0xe1, 0x00, 0x55, + 0xf0, 0x5f, 0x87, 0xca, 0xf9, 0xdb, 0x32, 0x42, 0x14, 0x66, 0xf0, 0xf4, 0xab, 0x93, 0xf8, 0xc3, + 0x0a, 0x50, 0x59, 0x1b, 0x8e, 0x0a, 0xbb, 0xc3, 0xc8, 0x14, 0x54, 0xb8, 0x8b, 0x72, 0xfa, 0xe4, + 0x33, 0x8c, 0x73, 0xe5, 0x6f, 0x81, 0x10, 0x56, 0x73, 0x7d, 0xda, 0x49, 0x27, 0xe0, 0xab, 0x0a, + 0x3b, 0x76, 0x8e, 0x69, 0xc9, 0x72, 0x38, 0xb6, 0x75, 0x9a, 0x13, 0xba, 0x79, 0xd1, 0xea, 0x32, + 0x45, 0xb0, 0x92, 0xf1, 0x2e, 0x46, 0x67, 0xba, 0x03, 0x99, 0x2a, 0x83, 0xae, 0x5a, 0xe2, 0xd2, + 0x71, 0xb1, 0x12, 0xd6, 0x26, 0x00, 0xa6, 0x1a, 0x29, 0xd7, 0xee, 0x70, 0xbc, 0x2d, 0xd7, 0x6d, + 0xf7, 0x8f, 0xa4, 0x67, 0x29, 0x04, 0x1e, 0x42, 0xec, 0x74, 0x73, 0x15, 0x6b, 0x71, 0x87, 0xa6, + 0x12, 0x47, 0x09, 0x2f, 0xc6, 0x0f, 0x2e, 0xa8, 0x76, 0xb9, 0xb8, 0x1c, 0x30, 0x7e, 0xe8, 0xeb, + 0x2b, 0x74, 0x56, 0xfe, 0x04, 0xba, 0x33, 0x7b, 0xd0, 0x2e, 0x44, 0x44, 0x63, 0x43, 0x5b, 0x0d, + 0xa7, 0xd3, 0x13, 0x91, 0xfb, 0xcd, 0x58, 0xe7, 0x1c, 0x2d, 0x59, 0x0c, 0x28, 0xa0, 0x87, 0xc9, + 0x28, 0x5d, 0xaf, 0xfc, 0x11, 0xb6, 0x80, 0xf9, 0xab, 0xf1, 0xd3, 0x59, 0x71, 0xae, 0x57, 0x2e, + 0x77, 0x49, 0x5b, 0x9b, 0xb2, 0x06, 0x35, 0x35, 0x68, 0x46, 0xf3, 0x22, 0x61, 0x71, 0xe3, 0x30, + 0xe5, 0x2b, 0x43, 0x13, 0x52, 0xd3, 0xf0, 0xbd, 0x5e, 0xdb, 0xd0, 0xcd, 0x5f, 0x1a, 0xc6, 0x89, + 0x15, 0x8c, 0xd1, 0xe8, 0xa0, 0x68, 0xa0, 0xe0, 0xe7, 0x3c, 0x28, 0xc1, 0x1c, 0x10, 0x86, 0x6a, + 0x95, 0x1e, 0xb8, 0x35, 0xc7, 0x53, 0x68, 0x07, 0x3a, 0xb7, 0x6c, 0x98, 0xd4, 0x2c, 0x9f, 0x4e, + 0x75, 0x2d, 0x75, 0xc3, 0x6a, 0xcf, 0x7f, 0x99, 0xa8, 0x37, 0xa8, 0x77, 0xf9, 0x65, 0xb3, 0x1e, + 0x8d, 0x95, 0xde, 0xa4, 0xe3, 0x5f, 0x34, 0xf8, 0x6f, 0xd9, 0x0b, 0xec, 0xb0, 0x79, 0x93, 0x0f, + 0x5f, 0x8c, 0x42, 0xc7, 0x1c, 0x84, 0xd3, 0xf2, 0x8a, 0xc5, 0x6b, 0xad, 0x96, 0x19, 0xf5, 0x30, + 0x31, 0x1e, 0xe7, 0x9e, 0x81, 0x5f, 0x62, 0x18, 0xe3, 0xa5, 0x6e, 0x5b, 0x07, 0x69, 0xef, 0xa2, + 0x25, 0xe8, 0xb4, 0x5f, 0xf6, 0xb5, 0x42, 0x03, 0x90, 0xc3, 0x28, 0xdd, 0x4f, 0xe9, 0x79, 0xeb, + 0x9b, 0xc0, 0xee, 0xfd, 0x90, 0x4c, 0xcb, 0x66, 0xcf, 0x19, 0x20, 0xd7, 0xea, 0x2b, 0xba, 0xd1, + 0x89, 0x19, 0x25, 0x21, 0x98, 0xa0, 0xc8, 0x76, 0xa3, 0xde, 0x1c, 0x9f, 0xd1, 0xa8, 0x5a, 0xd0, + 0xf7, 0x07, 0x15, 0x85, 0x64, 0x16, 0xcd, 0xf3, 0x02, 0x91, 0x89, 0x25, 0xe4, 0xa3, 0xf5, 0x81, + 0x9f, 0x1e, 0xea, 0xd4, 0xc6, 0xca, 0x84, 0x4f, 0x19, 0x33, 0x1e, 0xaa, 0xa2, 0x7e, 0x59, 0x13, + 0x77, 0x7f, 0x7a, 0x75, 0x3b, 0x9b, 0xc5, 0x0c, 0x25, 0x80, 0xf6, 0x48, 0xfb, 0x92, 0x0c, 0xf4, + 0x50, 0x7b, 0xa4, 0x24, 0x0e, 0x47, 0x89, 0xa0, 0x3e, 0xe5, 0x24, 0xea, 0xf1, 0xf6, 0x9b, 0xc5, + 0xda, 0x6b, 0x71, 0x33, 0x6b, 0x06, 0x2d, 0x82, 0xe6, 0x91, 0x11, 0xce, 0x30, 0x95, 0x3c, 0x8b, + 0x1f, 0x37, 0x9a, 0x40, 0xb6, 0x6a, 0x06, 0xa0, 0xff, 0xc2, 0x80, 0xe5, 0xd0, 0xbf, 0x6d, 0xdc, + 0xe5, 0x6b, 0xe0, 0xe4, 0xd2, 0x00, 0xd3, 0x95, 0x57, 0xdd, 0xc0, 0x6a, 0xaa, 0x26, 0xbc, 0x67, + 0x31, 0xc1, 0x46, 0x19, 0xbc, 0x2a, 0x65, 0xeb, 0xf3, 0xd3, 0x24, 0x63, 0x6d, 0x6e, 0x28, 0x7f, + 0xf7, 0xf6, 0xbf, 0x27, 0x79, 0xdc, 0x57, 0x27, 0x7e, 0x8e, 0xe7, 0x4e, 0xbf, 0xb7, 0x1c, 0x5a, + 0x78, 0xd5, 0xa3, 0x48, 0x09, 0x48, 0x6b, 0x2a, 0x6e, 0xfd, 0xfa, 0x52, 0xc5, 0xe4, 0x17, 0x99, + 0xba, 0x9c, 0x06, 0x25, 0xf8, 0xdf, 0x2e, 0x9e, 0xf2, 0xda, 0x08, 0xf8, 0x46, 0x6a, 0x59, 0x62, + 0xba, 0x33, 0x85, 0xa7, 0x07, 0x3a, 0xb5, 0xa0, 0xef, 0xac, 0xf1, 0x0d, 0xe4, 0x2b, 0x2e, 0xea, + 0x1a, 0x02, 0x8a, 0x3f, 0xfc, 0x0c, 0x8b, 0xa5, 0xe4, 0x12, 0x0e, 0x2a, 0x58, 0x0a, 0x5f, 0x78, + 0x49, 0x48, 0x42, 0x2c, 0x1f, 0xa9, 0x92, 0x63, 0xe1, 0x6f, 0xb3, 0x8c, 0x19, 0xd5, 0xb0, 0xa7, + 0x0b, 0x10, 0xa0, 0x4f, 0x62, 0x1a, 0xab, 0x67, 0x89, 0x30, 0xd3, 0xcd, 0x40, 0x55, 0xbf, 0x40, + 0xcb, 0x32, 0x38, 0x88, 0xe3, 0x71, 0xe2, 0x06, 0x5e, 0x43, 0xc4, 0x1f, 0xe1, 0xc9, 0xb7, 0xa0, + 0x52, 0x8d, 0xe7, 0x32, 0xdb, 0xec, 0x1a, 0x1a, 0x26, 0x94, 0xfe, 0x31, 0x91, 0xb0, 0xa0, 0xd2, + 0x7f, 0xc7, 0xde, 0x3a, 0xc5, 0x5b, 0x5f, 0x69, 0xd5, 0x18, 0xe3, 0x9d, 0xd1, 0x26, 0xe3, 0xca, + 0x7f, 0xe7, 0xb8, 0x2e, 0x06, 0x32, 0xee, 0x6f, 0xb5, 0xb9, 0xc2, 0xb0, 0x54, 0xd0, 0x93, 0x09, + 0x81, 0x6e, 0x63, 0x7c, 0x33, 0x06, 0x44, 0x2f, 0xd1, 0x6f, 0x37, 0x00, 0x39, 0xe3, 0x1b, 0xa0, + 0xb2, 0x39, 0x4b, 0xdc, 0x68, 0x8b, 0x06, 0xdf, 0x5f, 0x61, 0x63, 0xf9, 0x1a, 0x1b, 0xa3, 0x2e, + 0x07, 0xa5, 0x8d, 0x49, 0xe1, 0xf6, 0x2f, 0xb5, 0x69, 0x7d, 0x8d, 0x9b, 0x66, 0xb5, 0x37, 0x34, + 0x38, 0x7e, 0x09, 0x62, 0xe1, 0xc4, 0x0c, 0xdf, 0x1b, 0xa3, 0x3b, 0x3c, 0x10, 0x43, 0xd1, 0x48, + 0xae, 0x8d, 0x93, 0x13, 0xa7, 0x6b, 0x34, 0x7e, 0x81, 0x89, 0x60, 0x30, 0x6f, 0xef, 0x88, 0xa7, + 0x1f, 0x0b, 0x76, 0x24, 0x93, 0x42, 0xbe, 0xb4, 0x53, 0x32, 0xef, 0x75, 0xc8, 0x2d, 0xd0, 0xd1, + 0xc8, 0x3e, 0xbd, 0x4a, 0xef, 0x2d, 0xa9, 0x66, 0x90, 0xa9, 0x0b, 0x13, 0xbd, 0xe0, 0x72, 0xdb, + 0x3f, 0x2b, 0xac, 0xf9, 0xfb, 0xa8, 0xb8, 0xb2, 0xe4, 0xbd, 0xf6, 0xaf, 0xb0, 0x4d, 0x84, 0x7b, + 0x97, 0x28, 0x5b, 0xd6, 0xec, 0x55, 0x0d, 0x94, 0x36, 0xc9, 0x2b, 0x4c, 0x1c, 0xbe, 0x1e, 0xe7, + 0x25, 0x1b, 0xc7, 0x19, 0x3d, 0x3d, 0x15, 0x0d, 0xa0, 0x2f, 0x1b, 0x22, 0xbe, 0x48, 0x92, 0x14, + 0xef, 0xbe, 0x61, 0xbd, 0xb3, 0x74, 0x5f, 0x47, 0x83, 0xde, 0xa7, 0x65, 0x78, 0x77, 0xf2, 0x11, + 0x1d, 0x79, 0x31, 0xdb, 0x81, 0x64, 0x9b, 0x0a, 0x17, 0xbf, 0xca, 0xc0, 0x55, 0xdb, 0x30, 0x8b, + 0x0c, 0xb0, 0xfc, 0xb6, 0xe7, 0xf5, 0xf4, 0xe7, 0xf5, 0x8c, 0x08, 0xd2, 0x7a, 0x75, 0x20, 0x51, + 0xda, 0xc4, 0x45, 0x6a, 0x7d, 0xfc, 0xa1, 0xed, 0x0c, 0x29, 0x39, 0xca, 0xb4, 0x7a, 0xa0, 0xa8, + 0x23, 0xa4, 0x4c, 0x1e, 0xa2, 0x1d, 0x24, 0xfb, 0x87, 0x3e, 0x1d, 0x24, 0x3d, 0x75, 0x90, 0xe0, + 0x4b, 0x66, 0xfd, 0x7a, 0x0a, 0x93, 0xe5, 0xb0, 0x65, 0x74, 0x64, 0xbc, 0xdc, 0x90, 0x2d, 0xa3, + 0x13, 0xca, 0x09, 0xe7, 0xca, 0x22, 0xa8, 0x77, 0xb5, 0x05, 0x40, 0x1a, 0x52, 0x68, 0x05, 0x88, + 0x32, 0x25, 0x51, 0x0a, 0x6a, 0xe4, 0x64, 0x83, 0xd6, 0x0f, 0x7e, 0xc8, 0xd9, 0x4b, 0x1d, 0xa5, + 0x86, 0xcf, 0x66, 0x67, 0xdd, 0xf0, 0x68, 0xf5, 0x76, 0x3a, 0xac, 0xb3, 0x33, 0x15, 0x98, 0xa2, + 0x0a, 0x25, 0xe7, 0xc4, 0xa1, 0xf2, 0xb5, 0x03, 0x95, 0x18, 0x39, 0x1c, 0x69, 0xc7, 0xda, 0xe9, + 0x38, 0xc6, 0x58, 0xe5, 0xe8, 0xdc, 0xfe, 0xce, 0x06, 0x89, 0x13, 0xf4, 0x54, 0x76, 0xf6, 0xf0, + 0x98, 0x10, 0x13, 0x76, 0xf4, 0x89, 0x35, 0x92, 0x15, 0xe6, 0x63, 0x11, 0x33, 0xb6, 0xc7, 0x1c, + 0xde, 0x12, 0x84, 0xcc, 0x92, 0x34, 0xf0, 0x77, 0x7c, 0x5b, 0x65, 0x73, 0xb9, 0x3c, 0x26, 0xf3, + 0x01, 0x2a, 0xc3, 0x39, 0xcb, 0xa4, 0x0c, 0xd8, 0xcf, 0x45, 0x45, 0x1e, 0x6e, 0x35, 0x0c, 0x65, + 0x35, 0xb3, 0x50, 0x1d, 0x79, 0xbd, 0x2a, 0x20, 0x65, 0x8d, 0x96, 0x78, 0xbe, 0x60, 0xa5, 0x41, + 0x6b, 0xb5, 0x5d, 0x5c, 0x48, 0x0f, 0x8e, 0x2f, 0x41, 0xd0, 0x6b, 0xc1, 0xd7, 0xa6, 0x11, 0x42, + 0x79, 0xe6, 0x5f, 0x54, 0x07, 0x13, 0xa3, 0x25, 0xe7, 0x51, 0x06, 0x87, 0xaf, 0x90, 0x94, 0x4c, + 0xb1, 0x08, 0x35, 0x4d, 0x45, 0xd6, 0x9e, 0x40, 0xd1, 0x68, 0x14, 0x91, 0x48, 0xfd, 0x5c, 0x8a, + 0x80, 0x77, 0xd4, 0xfd, 0x03, 0x6c, 0x52, 0x93, 0x81, 0x38, 0xd4, 0xb2, 0x26, 0x02, 0xb5, 0x82, + 0x3f, 0x69, 0xf6, 0x11, 0x93, 0x74, 0xe3, 0x95, 0x1b, 0x6d, 0x13, 0x90, 0x27, 0x83, 0x32, 0xe0, + 0x3c, 0x48, 0xe0, 0x8c, 0x00, 0xd5, 0x98, 0x7c, 0x67, 0xc4, 0x3a, 0x37, 0xd4, 0xf8, 0xa2, 0x54, + 0xd8, 0xe6, 0x3a, 0xc0, 0x24, 0xbf, 0x83, 0x41, 0x75, 0x7a, 0xcb, 0x12, 0x2d, 0x4a, 0x3b, 0x2b, + 0x2a, 0x36, 0x6a, 0xe8, 0xb3, 0x66, 0x89, 0x06, 0xc6, 0xf5, 0xfa, 0xf0, 0xf0, 0xa0, 0xcb, 0x79, + 0x97, 0xdf, 0xdd, 0x87, 0x23, 0x87, 0x65, 0xf0, 0xa1, 0xa7, 0x4b, 0xcc, 0xa4, 0xc5, 0xd7, 0x5e, + 0x8a, 0xe4, 0x73, 0x55, 0x25, 0x7e, 0xaf, 0x27, 0x45, 0xd5, 0xe7, 0x0d, 0x43, 0x0d, 0xc2, 0x6f, + 0x1e, 0xc4, 0x97, 0x8d, 0xc6, 0x60, 0xa8, 0xfd, 0x34, 0x14, 0x2b, 0x5f, 0xff, 0xce, 0xc3, 0xa9, + 0x7e, 0xe9, 0xc4, 0xad, 0x44, 0xcf, 0x78, 0xe7, 0xd2, 0xbe, 0xda, 0xfe, 0xce, 0xe7, 0xff, 0xd4, + 0x70, 0xec, 0x5a, 0xf6, 0x94, 0x60, 0x06, 0x08, 0xc7, 0x7f, 0xd4, 0x1b, 0x69, 0x2e, 0x48, 0x22, + 0x3b, 0x28, 0xd6, 0x2a, 0xb3, 0xd7, 0x90, 0xf2, 0xbb, 0x85, 0x36, 0x00, 0x31, 0x79, 0xdf, 0x98, + 0xbc, 0x7f, 0x7e, 0xe4, 0x97, 0xf6, 0x90, 0xea, 0x6f, 0x41, 0x31, 0x6f, 0xba, 0x2b, 0xfe, 0x17, + 0x72, 0x1d, 0xc1, 0xb1, 0x37, 0xe1, 0x3b, 0xd2, 0x5e, 0xdf, 0xf3, 0x7d, 0x93, 0x07, 0x91, 0x93, + 0xa3, 0xc1, 0x86, 0x74, 0x13, 0x95, 0xef, 0xba, 0x15, 0x33, 0xfb, 0x73, 0xd8, 0xe9, 0xf3, 0xac, + 0xec, 0xea, 0x0c, 0xd9, 0xcc, 0xce, 0x7e, 0x4c, 0xb9, 0x6d, 0x15, 0xeb, 0xd5, 0x2c, 0xed, 0xad, + 0x9a, 0x57, 0xcb, 0x73, 0xe3, 0xda, 0x83, 0xff, 0x51, 0x51, 0xba, 0xfe, 0x61, 0x6a, 0x5d, 0xf2, + 0xd1, 0x6a, 0xfd, 0x0d, 0x33, 0xbf, 0x9e, 0x44, 0xfe, 0x38, 0x5f, 0x9b, 0xfc, 0x8c, 0x84, 0xca, + 0xd2, 0x74, 0x35, 0x68, 0xa9, 0xd6, 0x60, 0xff, 0x29, 0xd6, 0x46, 0xdf, 0x89, 0xa6, 0xfb, 0x4d, + 0x4d, 0xa9, 0x49, 0x5f, 0xf4, 0x60, 0x0c, 0x9a, 0x2e, 0x83, 0x59, 0xc3, 0x2b, 0x96, 0xc1, 0xcb, + 0x45, 0x8b, 0xad, 0x2c, 0x50, 0x31, 0x46, 0x97, 0x98, 0x28, 0xf5, 0x06, 0x28, 0x82, 0x4d, 0x4f, + 0xb1, 0xa2, 0xb3, 0xaa, 0x2f, 0x57, 0x76, 0xd3, 0x8e, 0x75, 0xa6, 0xd0, 0xbd, 0xd5, 0x6a, 0x22, + 0x98, 0x7c, 0xff, 0xec, 0x7c, 0x59, 0xc1, 0x5e, 0xe2, 0x18, 0x77, 0x04, 0xbc, 0x84, 0xf7, 0x76, + 0x1c, 0x61, 0x06, 0x21, 0xab, 0x12, 0xf4, 0xfb, 0xee, 0x27, 0x20, 0xa1, 0x2f, 0x31, 0x01, 0x5a, + 0xe9, 0x73, 0x53, 0xb8, 0xd5, 0xc7, 0x70, 0x84, 0x12, 0xcd, 0x45, 0x09, 0x1f, 0x85, 0x7d, 0x30, + 0xea, 0xa3, 0x3d, 0x98, 0x9c, 0x69, 0xef, 0x71, 0xb9, 0xa4, 0x44, 0xad, 0xe5, 0xe6, 0xe1, 0x88, + 0x8a, 0x2b, 0xa8, 0x41, 0x5b, 0x5c, 0xee, 0x37, 0x90, 0x3c, 0x2f, 0x82, 0x3f, 0x7f, 0x56, 0x04, + 0x3f, 0xda, 0x05, 0xfd, 0xad, 0x20, 0x97, 0x57, 0x3d, 0xad, 0x4d, 0x40, 0xdf, 0xd2, 0x0c, 0xb0, + 0x61, 0x7b, 0xc5, 0x9b, 0x48, 0xab, 0x97, 0x06, 0x9b, 0x2d, 0x93, 0x17, 0x05, 0xda, 0xf2, 0x26, + 0xfd, 0xc2, 0xe3, 0x28, 0xc1, 0xf0, 0xba, 0x8a, 0x34, 0xeb, 0xd7, 0x2e, 0x61, 0x13, 0x84, 0xed, + 0xcb, 0x81, 0x45, 0xdd, 0xf5, 0x63, 0xef, 0x26, 0xea, 0x87, 0x5e, 0x9a, 0xf4, 0x25, 0xaa, 0x37, + 0x3b, 0xf7, 0xc6, 0xf3, 0xa8, 0xbf, 0x7e, 0x19, 0x08, 0xcc, 0x50, 0xad, 0x1b, 0xbc, 0xd3, 0x74, + 0xb9, 0x1c, 0xac, 0x42, 0x55, 0xd0, 0x70, 0x08, 0xe6, 0xcf, 0xc5, 0x21, 0x98, 0x3c, 0x13, 0x87, + 0xc0, 0xcb, 0x36, 0x68, 0x90, 0xce, 0xb4, 0x37, 0x49, 0x94, 0x0e, 0xcf, 0x0c, 0xe6, 0x1e, 0xff, + 0x0c, 0xdd, 0x05, 0x13, 0xf1, 0x39, 0x9d, 0x05, 0xd9, 0x92, 0x7f, 0x04, 0x1a, 0x24, 0x07, 0x6b, + 0x91, 0xc7, 0xc5, 0xf4, 0x3d, 0x8b, 0xf4, 0x3b, 0x1c, 0x71, 0x7d, 0xfc, 0xff, 0x14, 0xb5, 0x56, + 0x48, 0x81, 0x0c, 0x08, 0xf9, 0xd3, 0xd3, 0x56, 0xad, 0x3c, 0x3f, 0x0a, 0x12, 0x77, 0x2a, 0xb7, + 0x32, 0x61, 0x59, 0x09, 0x22, 0xff, 0x06, 0x52, 0xdb, 0x9c, 0x30, 0xc2, 0xe7, 0x12, 0x46, 0xfa, + 0x5c, 0xc2, 0x88, 0x9e, 0x49, 0x18, 0xb1, 0x22, 0x8c, 0xd0, 0x8b, 0x15, 0x61, 0xa4, 0xe2, 0x33, + 0x10, 0x46, 0xb4, 0xd4, 0x29, 0x20, 0xd6, 0x29, 0x40, 0xad, 0xdf, 0xa2, 0x44, 0xed, 0xd4, 0xe5, + 0x25, 0x4c, 0x50, 0x98, 0x30, 0x9e, 0x44, 0x91, 0x12, 0x15, 0xde, 0xdc, 0xc6, 0x45, 0x04, 0x22, + 0x64, 0x09, 0x61, 0x0a, 0xbf, 0x88, 0xbb, 0x17, 0x60, 0xe6, 0x24, 0x45, 0x95, 0x29, 0x85, 0xce, + 0x31, 0x9d, 0xa2, 0xec, 0x79, 0x77, 0xd7, 0x6b, 0xe3, 0xb7, 0xf8, 0x66, 0x7c, 0xce, 0x4b, 0x0d, + 0x3e, 0x4d, 0xa0, 0xc1, 0xed, 0x7c, 0x1a, 0xf9, 0xc3, 0x66, 0x1c, 0xad, 0xca, 0xa7, 0x39, 0x88, + 0x6f, 0x7b, 0xcf, 0xc0, 0x73, 0x36, 0xe2, 0x80, 0x0d, 0xfd, 0x8a, 0x54, 0x02, 0xed, 0x7d, 0x03, + 0x2b, 0xdb, 0x2a, 0x79, 0x99, 0xe1, 0x0e, 0x5b, 0xe6, 0x03, 0x68, 0x6f, 0xfe, 0x7c, 0xde, 0x67, + 0x7a, 0xdc, 0xaa, 0x3c, 0x1f, 0x32, 0xf3, 0xa0, 0x76, 0xd2, 0x2e, 0x66, 0xf3, 0x7f, 0xf6, 0xed, + 0xc2, 0x86, 0xed, 0xc0, 0x63, 0xdb, 0x30, 0x37, 0xab, 0xc3, 0xcf, 0x58, 0x10, 0x2d, 0x99, 0x16, + 0xc1, 0xcc, 0x02, 0x2d, 0xcc, 0x8f, 0x87, 0xd6, 0x6b, 0x61, 0xcd, 0xc2, 0xa7, 0x1c, 0xba, 0xc5, + 0x8c, 0xe9, 0x3d, 0x4c, 0xa3, 0xde, 0x7e, 0x1a, 0x17, 0x7f, 0x53, 0x63, 0xc1, 0x7f, 0x31, 0x7e, + 0x63, 0xc4, 0xf4, 0x4d, 0x60, 0x06, 0x48, 0xed, 0x7c, 0xc7, 0x63, 0xc5, 0xac, 0x6a, 0x1c, 0x5b, + 0x5f, 0xbc, 0x94, 0x1d, 0x05, 0xfe, 0xba, 0x71, 0x53, 0x19, 0xf1, 0xb5, 0xd3, 0x61, 0x7a, 0xa4, + 0x57, 0x73, 0x5a, 0x73, 0xc7, 0x96, 0xdd, 0xe8, 0x81, 0x6e, 0x05, 0x06, 0xe1, 0xaf, 0x8c, 0xf7, + 0xaa, 0x76, 0x73, 0xa1, 0xba, 0xa9, 0x46, 0x7c, 0xb1, 0x75, 0xd1, 0x5e, 0x75, 0xda, 0x98, 0x3d, + 0xf4, 0xb5, 0x54, 0xcb, 0x55, 0xaa, 0x12, 0xb1, 0x0b, 0xcf, 0x5c, 0x67, 0x89, 0xff, 0xd8, 0x18, + 0x24, 0xb7, 0xc9, 0x6a, 0xaf, 0xec, 0xe0, 0xb9, 0x6b, 0xae, 0xd0, 0x28, 0x7f, 0xe7, 0xa2, 0x1b, + 0xa8, 0x96, 0xbf, 0x73, 0xd5, 0xa1, 0xaf, 0x3e, 0xab, 0x2e, 0x36, 0xee, 0xdf, 0xca, 0xf6, 0x5d, + 0xbd, 0x69, 0xeb, 0xd1, 0xce, 0x6e, 0x95, 0x2b, 0x60, 0x40, 0xa8, 0xd3, 0xc4, 0x13, 0xf2, 0x87, + 0xf5, 0x1d, 0x1b, 0xe1, 0xa4, 0x6e, 0x03, 0xcf, 0x52, 0x41, 0xa4, 0x8d, 0x8f, 0x88, 0x36, 0x78, + 0x44, 0x2d, 0x0c, 0xb5, 0xe9, 0x31, 0x9f, 0xd2, 0x3a, 0x5f, 0x44, 0xfe, 0xc0, 0xaa, 0xd4, 0xca, + 0x33, 0x11, 0x96, 0x5a, 0x40, 0x96, 0x43, 0x9d, 0x81, 0x91, 0x90, 0x90, 0xf8, 0x52, 0x9a, 0x60, + 0x28, 0x8d, 0x9e, 0xf3, 0xf7, 0x93, 0x48, 0x8e, 0x9b, 0x49, 0xf7, 0x25, 0x79, 0x17, 0x23, 0x72, + 0x6a, 0x51, 0x3a, 0xad, 0x76, 0x5e, 0x44, 0xb6, 0x60, 0xb2, 0xc5, 0x39, 0x5b, 0x4e, 0xf6, 0x71, + 0xe5, 0xc5, 0xbe, 0x99, 0xd3, 0xda, 0x75, 0x9f, 0x9e, 0xb2, 0x8f, 0x47, 0x3d, 0x1c, 0x18, 0x34, + 0x6c, 0x33, 0x59, 0xbb, 0x9e, 0x0d, 0x0a, 0x94, 0x93, 0x7d, 0x6e, 0x53, 0x14, 0x8b, 0x07, 0x25, + 0x6c, 0x51, 0x57, 0x9f, 0x03, 0x34, 0xdb, 0xaa, 0x6b, 0x79, 0xbb, 0xf4, 0xcc, 0x72, 0x3b, 0xd9, + 0x47, 0x19, 0xe7, 0xb7, 0x68, 0x91, 0x5f, 0x9a, 0x74, 0x4f, 0xb7, 0x18, 0x61, 0x28, 0x94, 0xe8, + 0xb1, 0xf4, 0x16, 0xf0, 0x12, 0xbe, 0x9e, 0xf8, 0x97, 0xd0, 0xc7, 0xa1, 0x56, 0x34, 0x6e, 0xcd, + 0xe6, 0x3d, 0x36, 0x7d, 0x36, 0x92, 0x6e, 0xde, 0x5a, 0x37, 0x37, 0xeb, 0x6a, 0xc2, 0x5a, 0xde, + 0xd6, 0x24, 0xcc, 0xd4, 0xf9, 0x45, 0xa9, 0x98, 0x8d, 0x1c, 0xa1, 0xb9, 0x4c, 0xb3, 0xcc, 0x01, + 0x4a, 0x12, 0xcc, 0x6d, 0x1d, 0xe4, 0x5e, 0xe9, 0xd5, 0x23, 0xc3, 0xc7, 0x31, 0x20, 0xd0, 0xd9, + 0x38, 0xad, 0xb8, 0xcc, 0x15, 0x2e, 0x8c, 0x44, 0x8c, 0xae, 0xea, 0x28, 0xcb, 0xb2, 0x76, 0xf5, + 0xc2, 0xad, 0x45, 0x3c, 0x02, 0x31, 0xe7, 0x9e, 0x52, 0x3f, 0xcf, 0x1c, 0x7b, 0x61, 0xbb, 0xc3, + 0xdd, 0xde, 0xef, 0x7b, 0xee, 0xe9, 0x63, 0x52, 0x84, 0x0f, 0x22, 0x8a, 0x35, 0x4a, 0xac, 0xc9, + 0x2d, 0x08, 0x46, 0x37, 0x16, 0x4e, 0x5c, 0x1f, 0x01, 0x7f, 0x36, 0x8a, 0xc5, 0xe5, 0xe3, 0xf9, + 0x90, 0x6d, 0xf7, 0x5f, 0x32, 0x71, 0x61, 0x19, 0xa2, 0xb8, 0x98, 0x2c, 0xbc, 0x64, 0xd6, 0x98, + 0x81, 0xfa, 0x0d, 0xb2, 0x5f, 0x3a, 0x8d, 0x66, 0x8f, 0xb8, 0xbb, 0x28, 0xb8, 0x46, 0xde, 0x1f, + 0x2c, 0x39, 0xbd, 0xc0, 0x9f, 0x0c, 0xf7, 0x4f, 0x90, 0x7d, 0x04, 0x62, 0x00, 0x3d, 0xe1, 0xf3, + 0xa0, 0x4d, 0xe7, 0x14, 0xf7, 0x8f, 0xea, 0x05, 0xc7, 0x2a, 0x93, 0x2e, 0x82, 0x15, 0x74, 0xbf, + 0xc6, 0x41, 0x6c, 0x6c, 0x6a, 0x91, 0x6e, 0x1a, 0x37, 0x33, 0xdf, 0xc6, 0xd9, 0xc7, 0xfa, 0x3e, + 0x4e, 0x10, 0x6a, 0x36, 0x1d, 0x71, 0x77, 0xc7, 0xb3, 0xec, 0xe3, 0x79, 0x90, 0x98, 0xc9, 0x62, + 0xa1, 0x88, 0x8f, 0xb0, 0x5e, 0x9c, 0xd6, 0x8b, 0xee, 0xea, 0x45, 0x94, 0xb6, 0xb0, 0xaf, 0x3d, + 0x60, 0x01, 0x4a, 0xe9, 0x67, 0x0f, 0x13, 0xab, 0xdb, 0x6d, 0x4b, 0x87, 0x51, 0xce, 0x4c, 0xa4, + 0xc5, 0x4e, 0xd8, 0x7d, 0xfc, 0x48, 0x3c, 0x66, 0x2a, 0xdf, 0x65, 0xd7, 0x5e, 0x7a, 0x44, 0xbe, + 0x32, 0x15, 0x2e, 0x3d, 0x08, 0xc9, 0x99, 0x4a, 0x71, 0x4a, 0x5f, 0x63, 0xe3, 0xb7, 0xaf, 0x18, + 0x33, 0xf8, 0x35, 0x76, 0xdd, 0x7a, 0xde, 0x64, 0x4f, 0x37, 0xdd, 0x69, 0x12, 0x1f, 0xf7, 0x3b, + 0x90, 0x70, 0x3e, 0x04, 0x7a, 0x2c, 0x3d, 0xfd, 0xf0, 0x37, 0x57, 0x25, 0x8a, 0xd7, 0x4b, 0x03, + 0xdf, 0xd3, 0x7d, 0x22, 0xed, 0xcd, 0x3d, 0x15, 0x6c, 0x0e, 0xcc, 0x80, 0x42, 0xfd, 0x09, 0xfa, + 0x1f, 0x57, 0xfa, 0xed, 0x3d, 0xa7, 0x5f, 0x09, 0xc1, 0x8d, 0xbe, 0xf5, 0x76, 0xc9, 0xeb, 0x56, + 0xb2, 0x2f, 0x93, 0x28, 0x34, 0xe6, 0x2e, 0x06, 0x64, 0x9e, 0x3f, 0xb2, 0xa3, 0xfe, 0xc2, 0x38, + 0xc6, 0xe9, 0x9e, 0x6d, 0x9d, 0x51, 0x6f, 0x8a, 0x36, 0x8e, 0x81, 0xb1, 0xa0, 0x93, 0x64, 0x36, + 0x72, 0xcc, 0x07, 0x40, 0x2d, 0x3c, 0xe1, 0x4c, 0x7a, 0x82, 0xf1, 0xd6, 0xde, 0x1f, 0x39, 0xa1, + 0x71, 0x8b, 0xdb, 0x44, 0x44, 0xd1, 0xce, 0x7c, 0xdf, 0x7e, 0xce, 0x7a, 0xc9, 0x2b, 0x93, 0xe3, + 0x34, 0x01, 0x3e, 0x75, 0x23, 0x34, 0x33, 0xec, 0x42, 0x1b, 0x60, 0x60, 0x1e, 0xcb, 0x0a, 0xd4, + 0x02, 0xc1, 0x7f, 0xe1, 0x6b, 0xc0, 0x06, 0xf5, 0x59, 0x9b, 0xce, 0xa8, 0x13, 0xdd, 0x17, 0x95, + 0xb2, 0x58, 0x17, 0x6e, 0xb2, 0x1a, 0xe5, 0xa1, 0x38, 0xc3, 0xbe, 0xcf, 0x57, 0x22, 0x36, 0x14, + 0x27, 0x04, 0x7e, 0xe6, 0xcc, 0x2f, 0xc7, 0x98, 0x2a, 0x94, 0xe3, 0x79, 0x78, 0x84, 0xb5, 0xe1, + 0x7a, 0xc0, 0xf0, 0x26, 0x08, 0x8e, 0xc6, 0xd7, 0x86, 0x73, 0x8e, 0xed, 0xed, 0x2a, 0x7e, 0x58, + 0x2b, 0x4e, 0xc6, 0x6f, 0x0a, 0x1f, 0x43, 0x20, 0x7e, 0xd0, 0x70, 0x36, 0x42, 0xbc, 0xfb, 0xcd, + 0x26, 0xf5, 0xc5, 0x80, 0x4d, 0x27, 0x37, 0x6f, 0x54, 0x77, 0xae, 0x30, 0x2f, 0xa6, 0x72, 0x7d, + 0x3c, 0x16, 0xb8, 0x47, 0xf6, 0x1c, 0xf8, 0x74, 0x80, 0x00, 0xad, 0x20, 0x72, 0x2f, 0xae, 0xfa, + 0xbe, 0x97, 0xc3, 0x7f, 0x77, 0x7d, 0x4a, 0x52, 0xdc, 0xcd, 0x75, 0x27, 0x4a, 0x95, 0x93, 0x56, + 0xa0, 0x72, 0x77, 0x0e, 0x7d, 0x77, 0x30, 0x4d, 0x17, 0xac, 0x7b, 0xa5, 0x57, 0x3b, 0x78, 0x5d, + 0xa9, 0xe7, 0x2e, 0x61, 0x22, 0x31, 0x73, 0xa8, 0x30, 0x1c, 0x63, 0x42, 0xda, 0xab, 0x5d, 0x1a, + 0x11, 0xc2, 0xb7, 0xba, 0x03, 0x3e, 0x38, 0x28, 0x5c, 0x96, 0x0b, 0xcb, 0x38, 0x9c, 0x09, 0x2e, + 0xa0, 0xe3, 0xd7, 0x70, 0x47, 0xcb, 0x55, 0x14, 0x41, 0x57, 0xfa, 0x7a, 0xc3, 0x3b, 0xf1, 0x9a, + 0xbc, 0xb0, 0xf9, 0x12, 0xfd, 0x82, 0x6f, 0x58, 0x01, 0xf5, 0xcc, 0x8d, 0x65, 0x25, 0x87, 0x58, + 0xa7, 0xbd, 0xed, 0x1f, 0x11, 0x5d, 0x4a, 0x36, 0xbd, 0xac, 0x34, 0xdd, 0x5f, 0xd9, 0xf4, 0xbd, + 0xde, 0x74, 0x5c, 0x69, 0x7a, 0x20, 0x85, 0x23, 0x7d, 0x1a, 0x57, 0xec, 0xe1, 0x94, 0x62, 0xfa, + 0xbb, 0xf9, 0xed, 0x98, 0x47, 0xf7, 0x3b, 0x3d, 0xb4, 0x53, 0xe9, 0x54, 0x31, 0xe0, 0x29, 0x83, + 0x93, 0x0e, 0x1c, 0xb1, 0x45, 0x7a, 0x2a, 0x6a, 0xbd, 0x6e, 0xc4, 0x3b, 0x84, 0x0e, 0x27, 0x8a, + 0xb4, 0x92, 0x96, 0x0a, 0xc9, 0xac, 0x3d, 0x52, 0x5e, 0x02, 0xe6, 0x1e, 0x08, 0xd0, 0xb9, 0xb8, + 0x75, 0xb6, 0x7f, 0x25, 0x67, 0x73, 0xfe, 0xa4, 0xda, 0x56, 0x10, 0xee, 0xe5, 0x40, 0x6b, 0xc6, + 0x74, 0x6f, 0x19, 0x50, 0x9e, 0x5e, 0x92, 0x87, 0x85, 0xb8, 0x2d, 0x15, 0xe4, 0xe8, 0xa5, 0x41, + 0x34, 0x4f, 0xbb, 0xc7, 0xbc, 0x7e, 0x7e, 0xf7, 0x25, 0xfd, 0xe5, 0x72, 0x8c, 0x09, 0x63, 0xa2, + 0x00, 0x41, 0x06, 0xe1, 0xb4, 0x4d, 0xbb, 0xf3, 0x8e, 0xed, 0xe1, 0xdf, 0x4b, 0xf1, 0x77, 0x8c, + 0xc7, 0x2f, 0x8c, 0xe5, 0xf7, 0xe0, 0xfa, 0x92, 0x9c, 0x9f, 0x66, 0x6c, 0x0e, 0x5a, 0x87, 0x8d, + 0x10, 0xc2, 0xb6, 0x17, 0x99, 0x08, 0x94, 0x71, 0xfb, 0x6e, 0xfe, 0x93, 0x5a, 0x72, 0x7d, 0x6e, + 0xd7, 0x2c, 0x06, 0x41, 0xa1, 0x24, 0x6b, 0x58, 0x7a, 0x1e, 0x94, 0xd3, 0x74, 0x91, 0xb5, 0xe9, + 0x7b, 0x79, 0xed, 0xda, 0x5e, 0xef, 0x00, 0xf6, 0x32, 0xe5, 0x4d, 0xbd, 0x66, 0x8f, 0x08, 0x66, + 0xb7, 0xbd, 0x8d, 0xc8, 0x7f, 0x3f, 0xb1, 0x07, 0xc7, 0x60, 0xa5, 0x22, 0x06, 0x08, 0xb6, 0x7d, + 0x43, 0x0b, 0x65, 0x6d, 0x2d, 0x5b, 0xa8, 0x4e, 0x1a, 0x20, 0x60, 0xeb, 0xe4, 0x35, 0xd0, 0x69, + 0xb4, 0x54, 0x4d, 0x98, 0x46, 0xca, 0xaf, 0x5d, 0x0f, 0xe8, 0x94, 0x24, 0xe6, 0x72, 0xd7, 0xdb, + 0xdf, 0x21, 0xe6, 0x49, 0x59, 0xc9, 0xf7, 0x80, 0x94, 0x85, 0x04, 0x4d, 0x96, 0xbf, 0xb2, 0xe2, + 0x6c, 0x16, 0x86, 0x70, 0xe4, 0xb8, 0x4b, 0x8e, 0x7f, 0xab, 0x3f, 0xd0, 0x2d, 0xc1, 0x92, 0xf8, + 0x00, 0x7c, 0x8d, 0x99, 0xec, 0x57, 0x14, 0x38, 0xc9, 0x6e, 0xc4, 0xf9, 0x89, 0x78, 0x26, 0x8a, + 0xc8, 0xd0, 0x5a, 0x2b, 0x78, 0x05, 0x28, 0x49, 0x06, 0x6d, 0x32, 0x4c, 0x51, 0xd2, 0xaf, 0x14, + 0x1d, 0x5f, 0x85, 0x70, 0xfc, 0xc5, 0xb0, 0x16, 0xf9, 0x1d, 0x90, 0x1f, 0xfc, 0xe7, 0x57, 0x96, + 0xf0, 0xaf, 0xb0, 0x80, 0x6b, 0x1b, 0xad, 0xd8, 0x55, 0x35, 0x77, 0x7b, 0xec, 0xf4, 0x4f, 0xf5, + 0x4e, 0x9d, 0x05, 0xa7, 0xb0, 0xfe, 0x5a, 0xb2, 0x5c, 0x56, 0x3a, 0xc3, 0x2d, 0xa5, 0xaf, 0xb4, + 0xc8, 0x86, 0xb0, 0x82, 0x9d, 0x8a, 0x7e, 0xbc, 0xf6, 0x4a, 0x7f, 0xdc, 0xa4, 0xd2, 0xfb, 0x72, + 0x44, 0x17, 0xe6, 0x3b, 0x92, 0xef, 0x62, 0xd1, 0x23, 0x2b, 0x1f, 0x1e, 0xaf, 0x3c, 0xa3, 0x03, + 0xbd, 0xdd, 0x33, 0x44, 0xe4, 0xad, 0x96, 0xf5, 0x1a, 0xca, 0xf6, 0xa1, 0x6c, 0xed, 0xda, 0x53, + 0x22, 0x88, 0xfd, 0x2d, 0xb2, 0x26, 0x36, 0x13, 0xf1, 0xda, 0x63, 0xdb, 0x95, 0xd9, 0x66, 0x2a, + 0x87, 0xd2, 0x40, 0x1f, 0x4d, 0x70, 0x06, 0xb2, 0x07, 0x08, 0x3b, 0x97, 0xf0, 0xdf, 0xd8, 0xd3, + 0x9f, 0x74, 0x2e, 0x71, 0xbc, 0x48, 0x9a, 0x90, 0x92, 0x24, 0x99, 0x37, 0xa0, 0x83, 0xfe, 0xd9, + 0xd9, 0xb9, 0x47, 0xff, 0x3f, 0x47, 0xd3, 0x27, 0xd9, 0x3b, 0x11, 0x03, 0x8c, 0x77, 0xaa, 0x3f, + 0xa1, 0x5d, 0xba, 0x64, 0xc5, 0xfb, 0x30, 0x46, 0xbf, 0xa8, 0x66, 0xdb, 0xed, 0x64, 0x52, 0x54, + 0x0d, 0x74, 0x38, 0x88, 0xab, 0x89, 0x2e, 0x3f, 0x20, 0x88, 0xde, 0x1f, 0x7f, 0x42, 0x98, 0x47, + 0x7e, 0xc1, 0x8a, 0xdf, 0x25, 0xb4, 0xe3, 0xde, 0xde, 0x65, 0x54, 0x5c, 0xdd, 0x8e, 0xf1, 0xf6, + 0x62, 0xef, 0x5d, 0x34, 0x9f, 0xa4, 0x69, 0x7a, 0x1d, 0xb1, 0x3d, 0x84, 0x15, 0xdd, 0xbb, 0x8f, + 0x30, 0x57, 0xfc, 0x52, 0x98, 0x72, 0xe6, 0xb0, 0x62, 0x06, 0x42, 0x89, 0x0c, 0x13, 0x77, 0x9c, + 0xab, 0x49, 0x27, 0xe8, 0xbd, 0x71, 0x87, 0x07, 0x3e, 0x9e, 0x70, 0xf8, 0x74, 0xd7, 0xbb, 0x9a, + 0x0c, 0xf7, 0xe5, 0xd7, 0x03, 0x1f, 0x59, 0xc8, 0xab, 0x57, 0x41, 0x70, 0x35, 0xa1, 0x92, 0x4e, + 0x70, 0x80, 0x25, 0xfe, 0x1b, 0xad, 0x04, 0x3a, 0x68, 0x3c, 0xe8, 0xc2, 0x79, 0xa1, 0xf8, 0x29, + 0x97, 0x60, 0x2f, 0xae, 0x72, 0x74, 0xff, 0xb8, 0x9a, 0x2c, 0x3d, 0x0b, 0xa3, 0xc6, 0x3d, 0xeb, + 0xd0, 0xff, 0x03, 0x52, 0xa2, 0xeb, 0xbd, 0xed, 0x09, 0x10, 0x14, 0xe0, 0xc2, 0x73, 0x03, 0x67, + 0x09, 0x0a, 0x7e, 0x21, 0x03, 0x10, 0x87, 0x90, 0xc4, 0xdf, 0xdb, 0x19, 0x24, 0x09, 0xcf, 0x94, + 0x5c, 0x6b, 0xa0, 0x80, 0x4d, 0x1b, 0x64, 0x68, 0xa6, 0xcb, 0xd0, 0x52, 0x3a, 0xfe, 0x85, 0x8d, + 0xd3, 0x54, 0xe8, 0x2b, 0x0e, 0x1f, 0x06, 0xb0, 0x96, 0x1a, 0x14, 0x26, 0x28, 0x75, 0x81, 0xbd, + 0x27, 0x7c, 0xf9, 0xe4, 0x88, 0x4f, 0x4d, 0x68, 0x28, 0xcc, 0x56, 0xb6, 0x82, 0x8d, 0x8b, 0xfc, + 0x5f, 0x03, 0x39, 0x9f, 0x53, 0xf7, 0xd9, 0x83, 0xe5, 0xcf, 0x2f, 0xc7, 0x7a, 0x4a, 0x28, 0x32, + 0x72, 0x28, 0x5e, 0xad, 0xa3, 0xd9, 0xac, 0xd2, 0x11, 0xad, 0xa9, 0xba, 0xb7, 0xb1, 0x07, 0xfa, + 0x0d, 0xfb, 0x42, 0x64, 0x6d, 0xe5, 0x57, 0x31, 0x32, 0x8e, 0x93, 0x2e, 0x4d, 0x41, 0xb3, 0xd7, + 0xee, 0xdf, 0x59, 0xd0, 0x1b, 0x30, 0x71, 0xff, 0xce, 0x2a, 0xf7, 0xef, 0xe2, 0x1a, 0xa7, 0xfd, + 0xe2, 0xbd, 0x86, 0xa2, 0x23, 0xcd, 0xd1, 0xd5, 0x5c, 0x3e, 0x7c, 0x71, 0xc4, 0x08, 0x09, 0xa3, + 0x15, 0x14, 0xc0, 0x39, 0xe8, 0xd3, 0xb8, 0xb0, 0x98, 0xdb, 0xc5, 0xb1, 0xef, 0x61, 0x90, 0xd0, + 0xd7, 0x83, 0x2d, 0x02, 0x00, 0xf1, 0xc8, 0x33, 0x70, 0x44, 0x0b, 0x77, 0x20, 0x61, 0xb4, 0xa8, + 0x06, 0x5a, 0x8a, 0xef, 0x10, 0x36, 0x51, 0x5d, 0x52, 0xc0, 0x37, 0xf9, 0x1a, 0xf4, 0xa7, 0x83, + 0x36, 0x97, 0xf1, 0x65, 0x06, 0x76, 0xc5, 0x1c, 0xfd, 0xa8, 0x5c, 0x1a, 0xf5, 0x16, 0x4b, 0xef, + 0x52, 0x99, 0xd6, 0xf9, 0x84, 0x7c, 0xcf, 0x44, 0x0d, 0xd2, 0x46, 0x9e, 0xd7, 0x46, 0xee, 0x55, + 0x20, 0xa6, 0x16, 0x59, 0x5f, 0xef, 0xdf, 0x83, 0xf1, 0xf5, 0xf5, 0xc1, 0x2e, 0xeb, 0x6a, 0x88, + 0xc7, 0x0c, 0xf0, 0x1c, 0x86, 0x90, 0x39, 0x46, 0xea, 0x88, 0xea, 0x00, 0xf5, 0xa8, 0x21, 0x8e, + 0x02, 0x06, 0x8f, 0x7c, 0x18, 0xa1, 0xad, 0x22, 0x00, 0x29, 0x41, 0x80, 0x80, 0x25, 0x0d, 0x20, + 0x60, 0xc9, 0x52, 0x25, 0xd2, 0x48, 0xcc, 0x44, 0x1a, 0xbf, 0x03, 0xd7, 0xea, 0x1b, 0x01, 0xad, + 0x8c, 0xd7, 0x20, 0xc2, 0x5d, 0x45, 0x8e, 0x15, 0x78, 0x29, 0xc6, 0x22, 0x26, 0xdd, 0x0c, 0xfa, + 0x38, 0x4a, 0xba, 0x37, 0xa3, 0xda, 0x5a, 0x74, 0x7a, 0xb0, 0x1a, 0xfd, 0xc2, 0xf9, 0xfd, 0x19, + 0x3a, 0x44, 0xaa, 0x32, 0x2d, 0x0f, 0x05, 0x1d, 0x5a, 0xa0, 0x3d, 0xfc, 0x9a, 0x81, 0x88, 0x7b, + 0x1c, 0x22, 0x0e, 0xb0, 0x4a, 0x84, 0x21, 0xd1, 0x99, 0xda, 0x63, 0x3d, 0x6b, 0xd9, 0xca, 0xdc, + 0x7a, 0x5c, 0xa9, 0x4c, 0xb3, 0x74, 0xd4, 0x53, 0x6e, 0x3e, 0xc2, 0xb1, 0x46, 0x81, 0x57, 0x9b, + 0x8f, 0x33, 0xbf, 0xd5, 0xf3, 0x7d, 0xe9, 0x69, 0xbe, 0x22, 0x44, 0xdc, 0x28, 0xa3, 0x85, 0x23, + 0x19, 0x2d, 0x9c, 0x07, 0xc9, 0x59, 0x04, 0x0a, 0x52, 0x63, 0x4a, 0x2f, 0x4a, 0x3e, 0x24, 0x2c, + 0x8b, 0x5f, 0xd8, 0x43, 0x61, 0x4e, 0x5f, 0x59, 0x2b, 0x0b, 0x34, 0x95, 0x8e, 0xf2, 0xaa, 0x73, + 0x91, 0xdd, 0xcf, 0x1b, 0xfd, 0x8d, 0xb4, 0xeb, 0x6a, 0x23, 0x53, 0x9b, 0xb6, 0xd8, 0xcf, 0x59, + 0x56, 0x91, 0xf2, 0xa3, 0x2b, 0x01, 0x25, 0x3c, 0x99, 0x66, 0x0e, 0x83, 0xf9, 0x66, 0xe9, 0xe4, + 0xd6, 0xc0, 0x21, 0x41, 0x54, 0x94, 0xb0, 0x78, 0x37, 0x2f, 0x85, 0x59, 0x8f, 0x16, 0x56, 0x05, + 0x14, 0x23, 0x47, 0x37, 0xf3, 0x78, 0x31, 0xf4, 0xde, 0x74, 0xe5, 0x72, 0xd1, 0xb7, 0x01, 0x17, + 0xa7, 0x73, 0x4a, 0x21, 0x11, 0x90, 0x34, 0x44, 0xe5, 0xc1, 0x59, 0x7e, 0x8e, 0x80, 0xa7, 0x4e, + 0xc1, 0xeb, 0x89, 0x4e, 0xdd, 0xa3, 0xc4, 0x95, 0xd6, 0x15, 0xcc, 0x7c, 0x14, 0x1f, 0x25, 0xbb, + 0xc5, 0x20, 0xc6, 0x94, 0x7e, 0x54, 0x8b, 0xd8, 0x2d, 0xe3, 0x4e, 0xa2, 0xbb, 0xbd, 0x73, 0xb7, + 0x71, 0x10, 0x1a, 0x02, 0x1e, 0x90, 0xb2, 0x01, 0x89, 0x67, 0x0e, 0x07, 0x1d, 0x18, 0x0d, 0xfc, + 0x3b, 0x7d, 0x50, 0x5a, 0x7c, 0xab, 0x39, 0xb6, 0xea, 0xb8, 0xb4, 0x8a, 0x62, 0x78, 0xba, 0xff, + 0x28, 0x8e, 0xb2, 0x5c, 0x54, 0xe9, 0x9a, 0x22, 0x2c, 0x92, 0x88, 0xe3, 0x66, 0xf8, 0x0c, 0x68, + 0x5f, 0x1a, 0x55, 0x40, 0x0c, 0x98, 0x66, 0x35, 0x24, 0xf4, 0xb2, 0xd1, 0x7a, 0xbc, 0x7a, 0xe8, + 0x82, 0x69, 0x7d, 0x68, 0xc8, 0xf1, 0x7a, 0x2f, 0x15, 0xb0, 0xb4, 0xbe, 0x2c, 0xe0, 0x5f, 0x81, + 0xab, 0xe0, 0x9b, 0x6c, 0xbd, 0x82, 0x4f, 0x30, 0x46, 0xe5, 0x62, 0xe5, 0x30, 0x4d, 0x47, 0xb8, + 0xf2, 0x5a, 0x04, 0x48, 0x6c, 0x17, 0xfa, 0x6e, 0xbd, 0xd3, 0x49, 0x30, 0xa3, 0x46, 0x3d, 0x3f, + 0x8e, 0xd6, 0xf7, 0xd3, 0x53, 0xb2, 0x15, 0x18, 0xd8, 0xc6, 0x23, 0xa5, 0xd1, 0xee, 0xe3, 0x54, + 0x74, 0xf5, 0x16, 0xaf, 0xa4, 0xda, 0x97, 0x59, 0x89, 0x10, 0x28, 0x47, 0xaa, 0x1b, 0xb8, 0xc4, + 0x1d, 0x49, 0x97, 0xd8, 0x04, 0xe4, 0x78, 0xf1, 0x41, 0xd9, 0x65, 0xbd, 0x72, 0xe3, 0xa8, 0x5a, + 0x84, 0x4c, 0x04, 0x74, 0xa7, 0x0a, 0x44, 0x38, 0xaa, 0x5b, 0x7a, 0xd7, 0xaa, 0xb2, 0xa0, 0x0c, + 0x1d, 0xc7, 0xb2, 0x32, 0x4c, 0x1b, 0xbf, 0x21, 0x10, 0x42, 0xad, 0x2f, 0x84, 0xd9, 0x32, 0x3a, + 0xc2, 0xc0, 0x5c, 0x7f, 0x15, 0xfd, 0x94, 0xb2, 0x11, 0xf7, 0x92, 0x4d, 0x88, 0xcb, 0x6b, 0x7e, + 0xb1, 0xb0, 0x73, 0xfa, 0xcf, 0x68, 0x2e, 0x73, 0x25, 0x65, 0x61, 0x16, 0xfd, 0x15, 0x24, 0x70, + 0x28, 0x68, 0x79, 0x7b, 0xda, 0xed, 0x56, 0x90, 0x7b, 0xcd, 0x57, 0x39, 0x4d, 0x38, 0xc2, 0xbc, + 0x75, 0xe5, 0x3e, 0x8f, 0xae, 0x40, 0xb9, 0x17, 0x2a, 0x28, 0x24, 0x7a, 0x82, 0x93, 0x84, 0xd8, + 0x59, 0x69, 0xd5, 0x5b, 0x7d, 0xf1, 0x3c, 0x8e, 0x6f, 0xe7, 0xce, 0x7a, 0x4c, 0xe2, 0x35, 0xd5, + 0xf4, 0x8b, 0x60, 0x5e, 0x75, 0xc9, 0xf3, 0x04, 0xfc, 0xf7, 0x71, 0xfd, 0x4e, 0xbf, 0x8b, 0xa9, + 0x97, 0x43, 0xd8, 0x02, 0x73, 0xdb, 0xf5, 0x3e, 0x07, 0xaf, 0x28, 0x47, 0xe2, 0xbd, 0x17, 0xd1, + 0x90, 0x03, 0xdf, 0x7b, 0xf0, 0x05, 0x82, 0x29, 0xe1, 0xda, 0x9e, 0x42, 0x09, 0xee, 0x6b, 0x9c, + 0xb2, 0x26, 0x7b, 0xdf, 0x26, 0x1c, 0x9f, 0xb3, 0x4c, 0x6c, 0xc0, 0x63, 0x20, 0xa6, 0x5f, 0xd2, + 0x5b, 0x58, 0xa2, 0x7c, 0x54, 0x2d, 0x00, 0xfe, 0xdf, 0xd7, 0x92, 0xcf, 0x5d, 0x85, 0xf9, 0xc7, + 0x79, 0x4a, 0xe7, 0x04, 0xf6, 0xa2, 0xf2, 0xe6, 0x34, 0x24, 0xb9, 0x44, 0x04, 0x53, 0xc2, 0xd6, + 0x46, 0x69, 0x39, 0xff, 0x0d, 0xf4, 0x31, 0xc7, 0x86, 0xb6, 0xea, 0xde, 0x0d, 0x64, 0x65, 0xf1, + 0xa1, 0xa7, 0x4b, 0xbd, 0x93, 0x6b, 0x09, 0x87, 0x9f, 0x4d, 0x10, 0x53, 0xaf, 0x96, 0xad, 0x4a, + 0x19, 0xc5, 0xe9, 0x70, 0x31, 0x72, 0x4a, 0xc9, 0x43, 0x4d, 0x55, 0x81, 0xa3, 0x4b, 0xac, 0x5a, + 0xee, 0xa8, 0x68, 0x2b, 0xf7, 0xe9, 0x49, 0x9f, 0x46, 0x51, 0xf9, 0x9e, 0xc0, 0x77, 0x07, 0x16, + 0x53, 0x2e, 0x15, 0xf4, 0x86, 0x49, 0xbc, 0xfe, 0xa6, 0x16, 0xb6, 0xdd, 0xd4, 0x5f, 0x84, 0xe3, + 0x09, 0x17, 0xea, 0xe0, 0xe0, 0xe4, 0x2f, 0xe6, 0x5c, 0xe0, 0x0c, 0x7f, 0x49, 0x33, 0xef, 0xbf, + 0x8f, 0x1b, 0x12, 0x5b, 0x49, 0xb0, 0xe1, 0x2d, 0x47, 0xbe, 0x2e, 0xdf, 0x35, 0xc0, 0x23, 0xe8, + 0x76, 0x80, 0x2f, 0x09, 0xaf, 0xb1, 0xbd, 0x5d, 0x59, 0x9a, 0xda, 0x48, 0x93, 0xa0, 0xd8, 0x7d, + 0x40, 0x30, 0x75, 0xb2, 0x77, 0x93, 0xa8, 0x97, 0xb8, 0x5e, 0x1c, 0x74, 0x9c, 0xfc, 0x65, 0xb2, + 0x77, 0x0f, 0xc2, 0x61, 0xfa, 0x63, 0xf4, 0xc0, 0xa6, 0xce, 0xbe, 0x3b, 0xf0, 0xb7, 0x02, 0x44, + 0xdf, 0xe2, 0xa3, 0x1d, 0xfa, 0x20, 0x54, 0x1e, 0x51, 0xf6, 0x26, 0x5e, 0x70, 0x44, 0xa9, 0x22, + 0xb0, 0x20, 0x1e, 0x76, 0x7b, 0xfb, 0xdb, 0xdb, 0xdf, 0x3a, 0x79, 0xd8, 0xb6, 0x7c, 0xf9, 0xa0, + 0x6b, 0x58, 0x87, 0x46, 0xb3, 0x66, 0x64, 0x0b, 0x6a, 0xde, 0x0d, 0x72, 0x1c, 0x6e, 0x6f, 0x37, + 0x96, 0x36, 0xce, 0x70, 0x0c, 0xcc, 0xff, 0x3a, 0x17, 0xa3, 0x02, 0xd9, 0xbe, 0xad, 0x0f, 0x50, + 0xe4, 0x62, 0x77, 0xd3, 0x95, 0xee, 0x41, 0x47, 0x62, 0xdb, 0xe8, 0x9e, 0x9e, 0x84, 0x94, 0xb7, + 0xb8, 0x0f, 0x6a, 0xb8, 0x98, 0xcf, 0x05, 0xd8, 0x94, 0xfa, 0x88, 0xc4, 0xd9, 0x6c, 0xc0, 0xd5, + 0x1c, 0xb4, 0x69, 0xc1, 0x78, 0x89, 0x27, 0xdf, 0xe8, 0x4f, 0x0c, 0x83, 0x92, 0x07, 0xf9, 0x71, + 0x1a, 0xe3, 0x1c, 0x71, 0x12, 0xac, 0x63, 0x67, 0x0f, 0x78, 0x11, 0x24, 0xca, 0xc6, 0x57, 0x8d, + 0x18, 0x73, 0x69, 0x51, 0xe9, 0x44, 0x34, 0xe3, 0x78, 0x82, 0x0e, 0xac, 0xf4, 0xab, 0xb2, 0x8f, + 0x22, 0x2b, 0xfb, 0x15, 0x00, 0x9b, 0x44, 0x67, 0x75, 0xd8, 0x40, 0x51, 0xce, 0x51, 0x76, 0xd0, + 0x50, 0xc5, 0x09, 0xf2, 0x5d, 0x20, 0x28, 0xf3, 0x9d, 0xd7, 0xac, 0xce, 0x65, 0x93, 0x1b, 0xdb, + 0x13, 0x55, 0x5c, 0xf1, 0x21, 0x50, 0xdf, 0xef, 0x8f, 0x7a, 0xfb, 0x98, 0x4c, 0x4f, 0x74, 0xf2, + 0xf4, 0xb4, 0x45, 0x36, 0x30, 0x51, 0x8a, 0xab, 0xc9, 0x3f, 0x3f, 0x3d, 0xdd, 0x0f, 0x03, 0xad, + 0x90, 0x7f, 0xc1, 0x6d, 0x8b, 0x36, 0x20, 0xa0, 0x15, 0x1e, 0xc3, 0x21, 0xfa, 0x1b, 0x89, 0xc7, + 0x6c, 0xf5, 0xfa, 0xe2, 0x49, 0x98, 0x5c, 0x4b, 0x8d, 0xb9, 0x7c, 0x7c, 0x85, 0xd4, 0xfc, 0x76, + 0x3c, 0xd5, 0x13, 0x98, 0x86, 0x0e, 0xd9, 0xc7, 0xbb, 0x58, 0x97, 0xb3, 0x2a, 0x2d, 0x0d, 0x3e, + 0x57, 0xf4, 0x2e, 0x44, 0x3b, 0x18, 0x8e, 0x72, 0xb4, 0x85, 0x05, 0x82, 0x7a, 0x23, 0xdb, 0x47, + 0x85, 0xf0, 0xb6, 0x48, 0xed, 0x6f, 0x7d, 0xc1, 0x6a, 0x7f, 0xdc, 0x23, 0xe1, 0xaa, 0x11, 0xa2, + 0x5d, 0x09, 0xba, 0x7e, 0x85, 0x7f, 0xc8, 0x41, 0x11, 0xa8, 0x5c, 0x7b, 0xbb, 0x51, 0xce, 0x15, + 0x44, 0xfd, 0x84, 0xd8, 0xde, 0xb6, 0x53, 0x2a, 0x04, 0xc1, 0x07, 0x55, 0x55, 0xcc, 0x90, 0x06, + 0x43, 0xae, 0x88, 0xd0, 0x3a, 0xe4, 0x06, 0x03, 0x96, 0xfc, 0x03, 0x63, 0x19, 0x08, 0xad, 0xdd, + 0x6e, 0x57, 0x24, 0xc6, 0x91, 0x1a, 0x98, 0x32, 0xe6, 0xa8, 0x94, 0x38, 0x85, 0xca, 0xcf, 0x0d, + 0xf5, 0xb4, 0x11, 0x6c, 0x6f, 0xab, 0x2f, 0x20, 0x5c, 0xe0, 0x51, 0xc3, 0x5b, 0xa0, 0x8b, 0x9c, + 0x95, 0xb8, 0xe5, 0x8f, 0x88, 0x69, 0x33, 0xa2, 0xc3, 0xe6, 0xe9, 0xc9, 0x54, 0x70, 0x99, 0xb7, + 0x80, 0x52, 0xba, 0xae, 0xf6, 0xb4, 0x61, 0x41, 0x99, 0x47, 0xad, 0xdc, 0x7e, 0x63, 0x7d, 0xfc, + 0x6d, 0xa9, 0x6c, 0x64, 0xb5, 0xf9, 0x2c, 0x39, 0x87, 0x68, 0xe5, 0x41, 0x89, 0xed, 0xc1, 0xae, + 0x12, 0xdc, 0x23, 0x9c, 0x4e, 0x3f, 0xe0, 0xdd, 0x06, 0xb2, 0x22, 0x4c, 0xba, 0x4b, 0x56, 0x38, + 0x68, 0x6f, 0x13, 0x22, 0x27, 0xba, 0x7d, 0x61, 0x3f, 0xf5, 0x5a, 0x37, 0x29, 0x7a, 0x41, 0xa5, + 0xf7, 0xd0, 0x19, 0xf2, 0x8b, 0xf6, 0x8a, 0x05, 0x1e, 0xd4, 0x74, 0xc8, 0xae, 0xab, 0x49, 0x5d, + 0xa6, 0xb7, 0x50, 0x0f, 0xcf, 0x94, 0x35, 0xf5, 0x6e, 0xb3, 0x75, 0xd5, 0xe8, 0xc1, 0x20, 0x38, + 0x96, 0xf5, 0xfe, 0xcf, 0xd1, 0x1e, 0x70, 0xf9, 0x28, 0x2b, 0x86, 0xd6, 0xd1, 0x1e, 0xa2, 0x70, + 0xe3, 0xdf, 0xab, 0xe2, 0x26, 0x1e, 0x5a, 0xff, 0x17, 0x5b, 0x65, 0x11, 0x9e, 0x01, 0xa3, 0x01, + 0x00 }; diff --git a/wled00/palettes.h b/wled00/palettes.h index c9bdf3f696..e03f20b3e9 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -13,9 +13,16 @@ #ifndef PalettesWLED_h #define PalettesWLED_h -#define GRADIENT_PALETTE_COUNT 58 +// Redefine FastLED's gradient palette declaration: +#define DEFINE_PALETTE(X) const byte X[] PROGMEM = +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) -const byte ib_jul01_gp[] PROGMEM = { +// Gradient palette "ib_jul01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 bytes of program space. + +DEFINE_PALETTE(ib_jul01_gp) { 0, 194, 1, 1, 94, 1, 29, 18, 132, 57,131, 28, @@ -26,20 +33,19 @@ const byte ib_jul01_gp[] PROGMEM = { // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_vintage_57_gp[] PROGMEM = { +DEFINE_PALETTE( es_vintage_57_gp ) { 0, 2, 1, 1, 53, 18, 1, 0, 104, 69, 29, 1, 153, 167,135, 10, 255, 46, 56, 4}; - // Gradient palette "es_vintage_01_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte es_vintage_01_gp[] PROGMEM = { +DEFINE_PALETTE( es_vintage_01_gp ) { 0, 4, 1, 1, 51, 16, 0, 1, 76, 97,104, 3, @@ -49,92 +55,124 @@ const byte es_vintage_01_gp[] PROGMEM = { 229, 4, 1, 1, 255, 4, 1, 1}; - // Gradient palette "es_rivendell_15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_rivendell_15_gp[] PROGMEM = { +DEFINE_PALETTE( es_rivendell_15_gp ) { 0, 1, 14, 5, 101, 16, 36, 14, 165, 56, 68, 30, 242, 150,156, 99, 255, 150,156, 99}; - // Gradient palette "rgi_15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -// Edited to be brighter - -const byte rgi_15_gp[] PROGMEM = { - 0, 4, 1, 70, - 31, 55, 1, 30, - 63, 255, 4, 7, - 95, 59, 2, 29, - 127, 11, 3, 50, - 159, 39, 8, 60, - 191, 112, 19, 40, - 223, 78, 11, 39, - 255, 29, 8, 59}; +DEFINE_PALETTE( rgi_15_gp ) { + 0, 4, 1, 31, + 31, 55, 1, 16, + 63, 197, 3, 7, + 95, 59, 2, 17, + 127, 6, 2, 34, + 159, 39, 6, 33, + 191, 112, 13, 32, + 223, 56, 9, 35, + 255, 22, 6, 38}; // Gradient palette "retro2_16_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 8 bytes of program space. -const byte retro2_16_gp[] PROGMEM = { +DEFINE_PALETTE( retro2_16_gp ) { 0, 188,135, 1, 255, 46, 7, 1}; - // Gradient palette "Analogous_1_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Analogous_1_gp[] PROGMEM = { +DEFINE_PALETTE( Analogous_1_gp ) { 0, 3, 0,255, 63, 23, 0,255, 127, 67, 0,255, 191, 142, 0, 45, 255, 255, 0, 0}; - // Gradient palette "es_pinksplash_08_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_pinksplash_08_gp[] PROGMEM = { +DEFINE_PALETTE( es_pinksplash_08_gp ) { 0, 126, 11,255, 127, 197, 1, 22, 175, 210,157,172, 221, 157, 3,112, 255, 157, 3,112}; +// Gradient palette "es_pinksplash_07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_pinksplash_07_gp ) { + 0, 229, 1, 1, + 61, 242, 4, 63, + 101, 255, 12,255, + 127, 249, 81,252, + 153, 255, 11,235, + 193, 244, 5, 68, + 255, 232, 1, 5}; + +// Gradient palette "Coral_reef_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Coral_reef.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( Coral_reef_gp ) { + 0, 40,199,197, + 50, 10,152,155, + 96, 1,111,120, + 96, 43,127,162, + 139, 10, 73,111, + 255, 1, 34, 71}; + +// Gradient palette "es_ocean_breeze_068_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_068.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_068_gp ) { + 0, 100,156,153, + 51, 1, 99,137, + 101, 1, 68, 84, + 104, 35,142,168, + 178, 0, 63,117, + 255, 1, 10, 10}; // Gradient palette "es_ocean_breeze_036_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 16 bytes of program space. -const byte es_ocean_breeze_036_gp[] PROGMEM = { +DEFINE_PALETTE( es_ocean_breeze_036_gp ) { 0, 1, 6, 7, 89, 1, 99,111, 153, 144,209,255, 255, 0, 73, 82}; - // Gradient palette "departure_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 88 bytes of program space. -const byte departure_gp[] PROGMEM = { +DEFINE_PALETTE( departure_gp ) { 0, 8, 3, 0, 42, 23, 7, 0, 63, 75, 38, 6, @@ -148,13 +186,12 @@ const byte departure_gp[] PROGMEM = { 212, 0, 55, 0, 255, 0, 55, 0}; - // Gradient palette "es_landscape_64_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -const byte es_landscape_64_gp[] PROGMEM = { +DEFINE_PALETTE( es_landscape_64_gp ) { 0, 0, 0, 0, 37, 2, 25, 1, 76, 15,115, 5, @@ -165,13 +202,12 @@ const byte es_landscape_64_gp[] PROGMEM = { 204, 59,117,250, 255, 1, 37,192}; - // Gradient palette "es_landscape_33_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte es_landscape_33_gp[] PROGMEM = { +DEFINE_PALETTE( es_landscape_33_gp ) { 0, 1, 5, 0, 19, 32, 23, 1, 38, 161, 55, 1, @@ -179,13 +215,12 @@ const byte es_landscape_33_gp[] PROGMEM = { 66, 39,142, 74, 255, 1, 4, 1}; - // Gradient palette "rainbowsherbet_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte rainbowsherbet_gp[] PROGMEM = { +DEFINE_PALETTE( rainbowsherbet_gp ) { 0, 255, 33, 4, 43, 255, 68, 25, 86, 255, 7, 25, @@ -194,13 +229,12 @@ const byte rainbowsherbet_gp[] PROGMEM = { 209, 42,255, 22, 255, 87,255, 65}; - // Gradient palette "gr65_hult_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte gr65_hult_gp[] PROGMEM = { +DEFINE_PALETTE( gr65_hult_gp ) { 0, 247,176,247, 48, 255,136,255, 89, 220, 29,226, @@ -208,13 +242,12 @@ const byte gr65_hult_gp[] PROGMEM = { 216, 1,124,109, 255, 1,124,109}; - // Gradient palette "gr64_hult_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte gr64_hult_gp[] PROGMEM = { +DEFINE_PALETTE( gr64_hult_gp ) { 0, 1,124,109, 66, 1, 93, 79, 104, 52, 65, 1, @@ -224,13 +257,12 @@ const byte gr64_hult_gp[] PROGMEM = { 239, 0, 55, 45, 255, 0, 55, 45}; - // Gradient palette "GMT_drywet_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte GMT_drywet_gp[] PROGMEM = { +DEFINE_PALETTE( GMT_drywet_gp ) { 0, 47, 30, 2, 42, 213,147, 24, 84, 103,219, 52, @@ -239,13 +271,12 @@ const byte GMT_drywet_gp[] PROGMEM = { 212, 1, 1,111, 255, 1, 7, 33}; - // Gradient palette "ib15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte ib15_gp[] PROGMEM = { +DEFINE_PALETTE( ib15_gp ) { 0, 113, 91,147, 72, 157, 88, 78, 89, 208, 85, 33, @@ -253,26 +284,35 @@ const byte ib15_gp[] PROGMEM = { 141, 137, 31, 39, 255, 59, 33, 89}; - -// Gradient palette "Tertiary_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/vermillion/tn/Tertiary_01.png.index.html +// Gradient palette "Fuschia_7_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/fuschia/tn/Fuschia-7.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Tertiary_01_gp[] PROGMEM = { - 0, 0, 1,255, - 63, 3, 68, 45, - 127, 23,255, 0, - 191, 100, 68, 1, - 255, 255, 1, 4}; +DEFINE_PALETTE( Fuschia_7_gp ) { + 0, 43, 3,153, + 63, 100, 4,103, + 127, 188, 5, 66, + 191, 161, 11,115, + 255, 135, 20,182}; +// Gradient palette "es_emerald_dragon_08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 bytes of program space. + +DEFINE_PALETTE( es_emerald_dragon_08_gp ) { + 0, 97,255, 1, + 101, 47,133, 1, + 178, 13, 43, 1, + 255, 2, 10, 1}; // Gradient palette "lava_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 52 bytes of program space. -const byte lava_gp[] PROGMEM = { +DEFINE_PALETTE( lava_gp ) { 0, 0, 0, 0, 46, 18, 0, 0, 96, 113, 0, 0, @@ -287,28 +327,26 @@ const byte lava_gp[] PROGMEM = { 244, 255,255, 71, 255, 255,255,255}; - -// Gradient palette "fierce_ice_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fierce-ice.png.index.html +// Gradient palette "fire_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fire.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte fierce_ice_gp[] PROGMEM = { - 0, 0, 0, 0, - 59, 0, 9, 45, - 119, 0, 38,255, - 149, 3,100,255, - 180, 23,199,255, - 217, 100,235,255, +DEFINE_PALETTE( fire_gp ) { + 0, 1, 1, 0, + 76, 32, 5, 0, + 146, 192, 24, 0, + 197, 220,105, 5, + 240, 252,255, 31, + 250, 252,255,111, 255, 255,255,255}; - // Gradient palette "Colorfull_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 44 bytes of program space. -const byte Colorfull_gp[] PROGMEM = { +DEFINE_PALETTE( Colorfull_gp ) { 0, 10, 85, 5, 25, 29,109, 18, 60, 59,138, 42, @@ -321,13 +359,26 @@ const byte Colorfull_gp[] PROGMEM = { 168, 100,180,155, 255, 22,121,174}; +// Gradient palette "Magenta_Evening_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Magenta_Evening.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( Magenta_Evening_gp ) { + 0, 71, 27, 39, + 31, 130, 11, 51, + 63, 213, 2, 64, + 70, 232, 1, 66, + 76, 252, 1, 69, + 108, 123, 2, 51, + 255, 46, 9, 35}; // Gradient palette "Pink_Purple_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 44 bytes of program space. -const byte Pink_Purple_gp[] PROGMEM = { +DEFINE_PALETTE( Pink_Purple_gp ) { 0, 19, 2, 39, 25, 26, 4, 45, 51, 33, 6, 52, @@ -340,13 +391,12 @@ const byte Pink_Purple_gp[] PROGMEM = { 183, 128, 57,155, 255, 146, 40,123}; - // Gradient palette "Sunset_Real_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte Sunset_Real_gp[] PROGMEM = { +DEFINE_PALETTE( Sunset_Real_gp ) { 0, 120, 0, 0, 22, 179, 22, 0, 51, 255,104, 0, @@ -355,74 +405,12 @@ const byte Sunset_Real_gp[] PROGMEM = { 198, 16, 0,130, 255, 0, 0,160}; - -// Gradient palette "Sunset_Yellow_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 44 bytes of program space. - -const byte Sunset_Yellow_gp[] PROGMEM = { - 0, 10, 62,123, - 36, 56,130,103, - 87, 153,225, 85, - 100, 199,217, 68, - 107, 255,207, 54, - 115, 247,152, 57, - 120, 239,107, 61, - 128, 247,152, 57, - 180, 255,207, 54, - 223, 255,227, 48, - 255, 255,248, 42}; - - -// Gradient palette "Beech_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Beech.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 60 bytes of program space. - -const byte Beech_gp[] PROGMEM = { - 0, 255,252,214, - 12, 255,252,214, - 22, 255,252,214, - 26, 190,191,115, - 28, 137,141, 52, - 28, 112,255,205, - 50, 51,246,214, - 71, 17,235,226, - 93, 2,193,199, - 120, 0,156,174, - 133, 1,101,115, - 136, 1, 59, 71, - 136, 7,131,170, - 208, 1, 90,151, - 255, 0, 56,133}; - - -// Gradient palette "Another_Sunset_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Another_Sunset.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte Another_Sunset_gp[] PROGMEM = { - 0, 110, 49, 11, - 29, 55, 34, 10, - 68, 22, 22, 9, - 68, 239,124, 8, - 97, 220,156, 27, - 124, 203,193, 61, - 178, 33, 53, 56, - 255, 0, 1, 52}; - - - - - // Gradient palette "es_autumn_19_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 52 bytes of program space. -const byte es_autumn_19_gp[] PROGMEM = { +DEFINE_PALETTE( es_autumn_19_gp ) { 0, 26, 1, 1, 51, 67, 4, 1, 84, 118, 14, 1, @@ -437,13 +425,12 @@ const byte es_autumn_19_gp[] PROGMEM = { 249, 17, 1, 1, 255, 17, 1, 1}; - // Gradient palette "BlacK_Blue_Magenta_White_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte BlacK_Blue_Magenta_White_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Blue_Magenta_White_gp ) { 0, 0, 0, 0, 42, 0, 0, 45, 84, 0, 0,255, @@ -452,26 +439,24 @@ const byte BlacK_Blue_Magenta_White_gp[] PROGMEM = { 212, 255, 55,255, 255, 255,255,255}; - // Gradient palette "BlacK_Magenta_Red_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte BlacK_Magenta_Red_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Magenta_Red_gp ) { 0, 0, 0, 0, 63, 42, 0, 45, 127, 255, 0,255, 191, 255, 0, 45, 255, 255, 0, 0}; - // Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Red_Magenta_Yellow_gp ) { 0, 0, 0, 0, 42, 42, 0, 0, 84, 255, 0, 0, @@ -480,377 +465,1632 @@ const byte BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { 212, 255, 55, 45, 255, 255,255, 0}; - // Gradient palette "Blue_Cyan_Yellow_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Blue_Cyan_Yellow_gp[] PROGMEM = { +DEFINE_PALETTE( Blue_Cyan_Yellow_gp ) { 0, 0, 0,255, 63, 0, 55,255, 127, 0,255,255, 191, 42,255, 45, 255, 255,255, 0}; +// Gradient palette "Sunset_Yellow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. -//Custom palette by Aircoookie - -const byte Orange_Teal_gp[] PROGMEM = { - 0, 0,150, 92, - 55, 0,150, 92, - 200, 255, 72, 0, - 255, 255, 72, 0}; - -//Custom palette by Aircoookie - -const byte Tiamat_gp[] PROGMEM = { - 0, 1, 2, 14, //gc - 33, 2, 5, 35, //gc from 47, 61,126 - 100, 13,135, 92, //gc from 88,242,247 - 120, 43,255,193, //gc from 135,255,253 - 140, 247, 7,249, //gc from 252, 69,253 - 160, 193, 17,208, //gc from 231, 96,237 - 180, 39,255,154, //gc from 130, 77,213 - 200, 4,213,236, //gc from 57,122,248 - 220, 39,252,135, //gc from 177,254,255 - 240, 193,213,253, //gc from 203,239,253 - 255, 255,249,255}; - -//Custom palette by Aircoookie - -const byte April_Night_gp[] PROGMEM = { - 0, 1, 5, 45, //deep blue - 10, 1, 5, 45, - 25, 5,169,175, //light blue - 40, 1, 5, 45, - 61, 1, 5, 45, - 76, 45,175, 31, //green - 91, 1, 5, 45, - 112, 1, 5, 45, - 127, 249,150, 5, //yellow - 143, 1, 5, 45, - 162, 1, 5, 45, - 178, 255, 92, 0, //pastel orange - 193, 1, 5, 45, - 214, 1, 5, 45, - 229, 223, 45, 72, //pink - 244, 1, 5, 45, - 255, 1, 5, 45}; - -const byte Orangery_gp[] PROGMEM = { - 0, 255, 95, 23, - 30, 255, 82, 0, - 60, 223, 13, 8, - 90, 144, 44, 2, - 120, 255,110, 17, - 150, 255, 69, 0, - 180, 158, 13, 11, - 210, 241, 82, 17, - 255, 213, 37, 4}; - -//inspired by Mark Kriegsman https://gist.github.com/kriegsman/756ea6dcae8e30845b5a -const byte C9_gp[] PROGMEM = { - 0, 184, 4, 0, //red - 60, 184, 4, 0, - 65, 144, 44, 2, //amber - 125, 144, 44, 2, - 130, 4, 96, 2, //green - 190, 4, 96, 2, - 195, 7, 7, 88, //blue - 255, 7, 7, 88}; - -const byte Sakura_gp[] PROGMEM = { - 0, 196, 19, 10, - 65, 255, 69, 45, - 130, 223, 45, 72, - 195, 255, 82,103, - 255, 223, 13, 17}; - -const byte Aurora_gp[] PROGMEM = { - 0, 1, 5, 45, //deep blue - 64, 0,200, 23, - 128, 0,255, 0, //green - 170, 0,243, 45, - 200, 0,135, 7, - 255, 1, 5, 45};//deep blue - -const byte Atlantica_gp[] PROGMEM = { - 0, 0, 28,112, //#001C70 - 50, 32, 96,255, //#2060FF - 100, 0,243, 45, - 150, 12, 95, 82, //#0C5F52 - 200, 25,190, 95, //#19BE5F - 255, 40,170, 80};//#28AA50 - - const byte C9_2_gp[] PROGMEM = { - 0, 6, 126, 2, //green - 45, 6, 126, 2, - 45, 4, 30, 114, //blue - 90, 4, 30, 114, - 90, 255, 5, 0, //red - 135, 255, 5, 0, - 135, 196, 57, 2, //amber - 180, 196, 57, 2, - 180, 137, 85, 2, //yellow - 255, 137, 85, 2}; - - //C9, but brighter and with a less purple blue - const byte C9_new_gp[] PROGMEM = { - 0, 255, 5, 0, //red - 60, 255, 5, 0, - 60, 196, 57, 2, //amber (start 61?) - 120, 196, 57, 2, - 120, 6, 126, 2, //green (start 126?) - 180, 6, 126, 2, - 180, 4, 30, 114, //blue (start 191?) - 255, 4, 30, 114}; - -// Gradient palette "temperature_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/arendal/tn/temperature.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 144 bytes of program space. - -const byte temperature_gp[] PROGMEM = { - 0, 1, 27,105, - 14, 1, 40,127, - 28, 1, 70,168, - 42, 1, 92,197, - 56, 1,119,221, - 70, 3,130,151, - 84, 23,156,149, - 99, 67,182,112, - 113, 121,201, 52, - 127, 142,203, 11, - 141, 224,223, 1, - 155, 252,187, 2, - 170, 247,147, 1, - 184, 237, 87, 1, - 198, 229, 43, 1, - 226, 171, 2, 2, - 240, 80, 3, 3, - 255, 80, 3, 3}; - - const byte Aurora2_gp[] PROGMEM = { - 0, 17, 177, 13, //Greenish - 64, 121, 242, 5, //Greenish - 128, 25, 173, 121, //Turquoise - 192, 250, 77, 127, //Pink - 255, 171, 101, 221 //Purple - }; - - // Gradient palette "bhw1_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_01.png.index.html +DEFINE_PALETTE( Sunset_Yellow_gp ) { + 0, 10, 62,123, + 36, 56,130,103, + 87, 153,225, 85, + 100, 199,217, 68, + 107, 255,207, 54, + 115, 247,152, 57, + 120, 239,107, 61, + 128, 247,152, 57, + 180, 255,207, 54, + 223, 255,227, 48, + 255, 255,248, 42}; + + +// Gradient palette "cloud_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/cloud.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 12 bytes of program space. -const byte retro_clown_gp[] PROGMEM = { - 0, 227,101, 3, - 117, 194, 18, 19, - 255, 92, 8,192}; +DEFINE_PALETTE( cloud_gp ) { + 0, 247,149, 91, + 127, 208, 32, 71, + 255, 42, 79,188}; + -// Gradient palette "bhw1_04_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_04.png.index.html +// Gradient palette "fireandice_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/fireandice.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. +// Size: 24 bytes of program space. -const byte candy_gp[] PROGMEM = { - 0, 229,227, 1, - 15, 227,101, 3, - 142, 40, 1, 80, - 198, 17, 1, 79, - 255, 0, 0, 45}; +DEFINE_PALETTE( fireandice_gp ) { + 0, 80, 2, 1, + 51, 206, 15, 1, + 101, 242, 34, 1, + 153, 16, 67,128, + 204, 2, 21, 69, + 255, 1, 2, 4}; -// Gradient palette "bhw1_05_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_05.png.index.html +// Gradient palette "bhw2_39_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_39.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 8 bytes of program space. +// Size: 28 bytes of program space. + +DEFINE_PALETTE( bhw2_39_gp ) { + 0, 2,184,188, + 33, 56, 27,162, + 66, 56, 27,162, + 122, 255,255, 45, + 150, 227, 65, 6, + 201, 67, 13, 27, + 255, 16, 1, 53}; + +// Gradient palette "tashangel_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/tashangel.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. -const byte toxy_reaf_gp[] PROGMEM = { - 0, 1,221, 53, - 255, 73, 3,178}; +DEFINE_PALETTE( tashangel_gp ) { + 0, 133, 68,197, + 51, 2, 1, 33, + 101, 50, 35,130, + 153, 199,225,237, + 204, 41,187,228, + 255, 133, 68,197}; -// Gradient palette "bhw1_06_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_06.png.index.html +// Gradient palette "butterflytalker_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/butterflytalker.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. +// Size: 28 bytes of program space. -const byte fairy_reaf_gp[] PROGMEM = { - 0, 184, 1,128, - 160, 1,193,182, - 219, 153,227,190, - 255, 255,255,255}; +DEFINE_PALETTE( butterflytalker_gp ) { + 0, 1, 1, 6, + 51, 6, 11, 52, + 89, 107,107,192, + 127, 101,161,192, + 165, 107,107,192, + 204, 6, 11, 52, + 255, 0, 0, 0}; -// Gradient palette "bhw1_14_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_14.png.index.html +// Gradient palette "os250k_metres_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/os/tn/os250k-metres.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( os250k_metres_gp ) { + 0, 255,255,255, + 11, 255,255,255, + 11, 255,252,214, + 34, 255,252,214, + 34, 255,248,178, + 57, 255,248,178, + 57, 255,211,130, + 81, 255,211,130, + 81, 255,176, 89, + 115, 255,176, 89, + 115, 255,147, 63, + 173, 255,147, 63, + 173, 255,127, 55, + 255, 255,127, 55}; + +// Gradient palette "Night_Midnight_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Night_Midnight.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -const byte semi_blue_gp[] PROGMEM = { +DEFINE_PALETTE( Night_Midnight_gp ) { + 0, 15, 25, 27, + 36, 22, 48, 91, + 59, 32, 80,203, + 74, 110,154,228, + 77, 255,255,255, + 82, 110,154,228, + 96, 32, 80,203, + 189, 5, 18, 73, + 255, 0, 1, 12}; + +// Gradient palette "Afterdusk_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Afterdusk.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( Afterdusk_gp ) { 0, 0, 0, 0, - 12, 1, 1, 3, - 53, 8, 1, 22, - 80, 4, 6, 89, - 119, 2, 25,216, - 145, 7, 10, 99, - 186, 15, 2, 31, - 233, 2, 1, 5, - 255, 0, 0, 0}; + 25, 1, 1, 1, + 48, 1, 1, 1, + 67, 41, 49, 52, + 70, 210,219,216, + 73, 155,115,137, + 81, 109, 46, 78, + 86, 109, 46, 78, + 97, 109, 46, 78, + 165, 50, 15, 79, + 255, 16, 1, 80}; + +// Gradient palette "BlueSky_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/BlueSky.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. -// Gradient palette "bhw1_three_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_three.png.index.html +DEFINE_PALETTE( BlueSky_gp ) { + 0, 1, 7, 39, + 25, 2, 25, 88, + 61, 9, 53,160, + 88, 46,115,201, + 102, 120,203,245, + 108, 88,169,230, + 124, 63,139,216, + 216, 21, 96,203, + 255, 2, 60,188}; + +// Gradient palette "Gold_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Gold_Orange.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. +// Size: 52 bytes of program space. -const byte pink_candy_gp[] PROGMEM = { - 0, 255,255,255, - 45, 7, 12,255, - 112, 227, 1,127, - 112, 227, 1,127, - 140, 255,255,255, - 155, 227, 1,127, - 196, 45, 1, 99, - 255, 255,255,255}; +DEFINE_PALETTE( Gold_Orange_gp ) { + 0, 244, 88, 11, + 21, 247,118, 26, + 40, 249,152, 50, + 62, 252,201, 82, + 72, 255,255,125, + 79, 255,211,119, + 83, 255,169,112, + 87, 255,211,119, + 94, 255,255,125, + 103, 244,207, 54, + 118, 237,164, 16, + 202, 242,124, 13, + 255, 244, 88, 11}; + +// Gradient palette "Analogous_02_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/vermillion/tn/Analogous_02.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. -// Gradient palette "bhw1_w00t_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_w00t.png.index.html +DEFINE_PALETTE( Analogous_02_gp ) { + 0, 32, 0,123, + 63, 110, 5, 79, + 127, 255, 23, 45, + 191, 255, 21, 30, + 255, 255, 18, 18}; + +// Gradient palette "Analogous_04a_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/pink/tn/Analogous_04a.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. +// Size: 36 bytes of program space. -const byte red_reaf_gp[] PROGMEM = { - 0, 3, 13, 43, - 104, 78,141,240, - 188, 255, 0, 0, - 255, 28, 1, 1}; +DEFINE_PALETTE( Analogous_04a_gp ) { + 0, 67, 55,255, + 42, 67, 55,255, + 84, 67, 55,255, + 84, 120, 33,255, + 127, 120, 33,255, + 170, 120, 33,255, + 170, 255, 23, 45, + 212, 255, 23, 45, + 255, 255, 23, 45}; + +// Gradient palette "Cyan_Orange_Stripped_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Cyan_Orange_Stripped.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 48 bytes of program space. + +DEFINE_PALETTE( Cyan_Orange_Stripped_gp ) { + 0, 1,108,212, + 60, 1,108,212, + 121, 1,108,212, + 121, 0, 0, 0, + 124, 0, 0, 0, + 127, 0, 0, 0, + 127, 229,127, 15, + 188, 242,186, 92, + 248, 255,255,255, + 248, 0, 0, 0, + 251, 0, 0, 0, + 255, 0, 0, 0}; +// Gradient palette "Cyan_White_Green_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Cyan_White_Green.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. -// Gradient palette "bhw2_23_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_23.png.index.html +DEFINE_PALETTE( Cyan_White_Green_gp ) { + 0, 0,255,255, + 63, 42,255,255, + 127, 255,255,255, + 191, 42,255, 45, + 255, 0,255, 0}; + +// Gradient palette "Wild_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/mp/tn/Wild_Orange.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Red & Flash in SR -// Size: 28 bytes of program space. +// Size: 56 bytes of program space. -const byte aqua_flash_gp[] PROGMEM = { +DEFINE_PALETTE( Wild_Orange_gp ) { 0, 0, 0, 0, - 66, 57,227,233, - 96, 255,255, 8, - 124, 255,255,255, - 153, 255,255, 8, - 188, 57,227,233, - 255, 0, 0, 0}; + 0, 144, 11, 1, + 0, 144, 11, 1, + 5, 144, 11, 1, + 10, 194, 36, 1, + 30, 252, 79, 1, + 86, 249,175,100, + 106, 244,122, 25, + 124, 237, 79, 1, + 157, 244,154, 2, + 196, 252,255, 5, + 209, 252,223, 3, + 239, 255,108, 1, + 255, 255, 36, 1}; + +// Gradient palette "IKat_Radial_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/mp/tn/IKat_Radial.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( IKat_Radial_gp ) { + 0, 3, 7, 4, + 56, 255,255,255, + 127, 3, 7, 4, + 196, 255,255,255, + 255, 3, 7, 4}; + +// Gradient palette "Citrus_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Citrus.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. -// Gradient palette "bhw2_xc_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_xc.png.index.html +DEFINE_PALETTE( Citrus_gp ) { + 0, 252,164, 5, + 63, 149,25, 3, + 135, 255,166, 9, + 201, 147,39, 3, + 255, 237,119, 4}; + +// Gradient palette "Teal_Blue_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Teal_Blue.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// YBlue in SR -// Size: 28 bytes of program space. +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Teal_Blue_gp ) { + 0, 1, 73, 88, + 63, 1, 43, 52, + 127, 1, 77, 95, + 196, 1, 58, 67, + 255, 1, 45, 50}; + +// Gradient palette "Ldby_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Ldby_Orange.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Ldby_Orange_gp ) { + 0, 217, 45, 17, + 61, 179, 21, 8, + 130, 222, 49, 21, + 193, 203, 32, 7, + 255, 173, 22, 6}; + +// Gradient palette "purple_orange_d07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/purple-orange-d07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( purple_orange_d07_gp ) { + 0, 53, 27, 91, + 36, 53, 27, 91, + 36, 121, 55,111, + 72, 121, 55,111, + 72, 179,107,137, + 109, 179,107,137, + 109, 179,189,182, + 145, 179,189,182, + 145, 234,152, 59, + 182, 234,152, 59, + 182, 227, 92, 11, + 218, 227, 92, 11, + 218, 165, 40, 1, + 255, 165, 40, 1}; + +// Gradient palette "blue_tan_d08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/blue-tan-d08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( blue_tan_d08_gp ) { + 0, 7, 77,210, + 31, 7, 77,210, + 31, 21,112,216, + 63, 21,112,216, + 63, 53,149,207, + 95, 53,149,207, + 95, 123,180,192, + 127, 123,180,192, + 127, 186,186,127, + 159, 186,186,127, + 159, 182,159, 50, + 191, 182,159, 50, + 191, 155,117, 14, + 223, 155,117, 14, + 223, 115, 72, 2, + 255, 115, 72, 2}; + +// Gradient palette "green_purple_d07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/green-purple-d07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( green_purple_d07_gp ) { + 0, 1, 90, 12, + 36, 1, 90, 12, + 36, 12,147, 51, + 72, 12,147, 51, + 72, 56,189,120, + 109, 56,189,120, + 109, 179,189,182, + 145, 179,189,182, + 145, 179,107,137, + 182, 179,107,137, + 182, 121, 55,111, + 218, 121, 55,111, + 218, 53, 27, 91, + 255, 53, 27, 91}; + +// Gradient palette "knoza_00_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/knoza/tn/knoza-00.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( knoza_00_gp ) { + 0, 56, 56,237, + 1, 115, 1, 1, + 24, 115, 1, 1, + 25, 237,130, 46, + 101, 237,186, 1, + 113, 237,186, 1, + 115, 2, 1, 1, + 138, 2, 1, 1, + 139, 237,186, 1, + 153, 237,186, 1, + 228, 237,130, 46, + 229, 115, 1, 1, + 253, 115, 1, 1, + 255, 56, 56,237}; +// Gradient palette "knoza_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/knoza/tn/knoza-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( knoza_18_gp ) { + 0, 8, 1, 1, + 2, 1,239, 1, + 51, 1,239, 1, + 52, 175,130, 1, + 100, 175,130, 1, + 101, 1, 1, 1, + 115, 1, 1, 1, + 117, 237,239,237, + 138, 237,239,237, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 175,130, 1, + 203, 175,130, 1, + 203, 1,239, 1, + 252, 1,239, 1, + 255, 8, 1, 1}; + +// Gradient palette "calpan_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/calpan/tn/calpan-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 40 bytes of program space. -const byte yelblu_hot_gp[] PROGMEM = { - 0, 4, 2, 9, - 58, 16, 0, 47, - 122, 24, 0, 16, - 158, 144, 9, 1, - 183, 179, 45, 1, - 219, 220,114, 2, - 255, 234,237, 1}; +DEFINE_PALETTE( calpan_18_gp ) { + 0, 133, 31,137, + 1, 117, 2, 88, + 24, 117, 2, 88, + 25, 239,241,245, + 32, 239,241,245, + 51, 239,241,245, + 53, 117, 2, 88, + 76, 117, 2, 88, + 77, 133, 31,137, + 255, 239,241,245}; + +// Gradient palette "calbayo_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/calbayo/tn/calbayo-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( calbayo_18_gp ) { + 0, 210,131, 1, + 60, 210,131, 1, + 62, 41, 2, 3, + 99, 41, 2, 3, + 100, 106, 40, 1, + 101, 210,131, 1, + 126, 210,131, 1, + 127, 210, 31, 6, + 165, 210, 31, 6, + 166, 210,131, 1, + 188, 210,131, 1, + 191, 3, 6, 6, + 226, 3, 6, 6, + 228, 210,131, 1, + 253, 210,131, 1, + 255, 1, 58, 29}; + +// Gradient palette "fib53_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( fib53_15_gp ) { + 0, 239, 11, 31, + 101, 239, 11, 31, + 101, 239,241,240, + 127, 239,241,240, + 128, 1, 1, 1, + 152, 1, 1, 1, + 153, 239,241,240, + 178, 239,241,240, + 179, 239, 11, 31, + 202, 239, 11, 31, + 203, 239,241,240, + 229, 239,241,240, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "grindylow_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( grindylow_15_gp ) { + 0, 101,241,105, + 127, 26,182,105, + 255, 26,151, 80}; + +// Gradient palette "grindylow_21_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-21.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. - // Gradient palette "bhw2_45_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_45.png.index.html +DEFINE_PALETTE( grindylow_21_gp ) { + 0, 101,241,105, + 51, 60,241,240, + 128, 26,241,240, + 191, 26,182,105, + 255, 26,151, 80}; + +// Gradient palette "konjo_08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( konjo_08_gp ) { + 0, 213,229,240, + 127, 213,229,240, + 128, 133,168,188, + 150, 133,168,188, + 150, 21, 57, 91, + 152, 0, 6, 33, + 177, 0, 6, 33, + 179, 0, 2, 9, + 200, 0, 2, 9, + 203, 0, 6, 33, + 227, 0, 6, 33, + 229, 30, 0, 2, + 252, 30, 0, 2, + 255, 0, 6, 33}; + +// Gradient palette "konjo_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 88 bytes of program space. + +DEFINE_PALETTE( konjo_18_gp ) { + 0, 109, 5, 1, + 13, 109, 5, 1, + 14, 133,168,188, + 37, 133,168,188, + 39, 0, 6, 33, + 63, 0, 6, 33, + 63, 77,130,184, + 87, 77,130,184, + 89, 0, 2, 9, + 114, 0, 2, 9, + 115, 213,229,240, + 140, 213,229,240, + 142, 0, 2, 9, + 165, 0, 2, 9, + 166, 77,130,184, + 191, 77,130,184, + 193, 0, 6, 33, + 216, 0, 6, 33, + 217, 133,168,188, + 240, 133,168,188, + 241, 109, 5, 1, + 255, 109, 5, 1}; + +// Gradient palette "konjo_19_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-19.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 88 bytes of program space. + +DEFINE_PALETTE( konjo_19_gp ) { + 0, 109, 5, 1, + 13, 109, 5, 1, + 14, 133,168,188, + 37, 133,168,188, + 39, 0, 6, 33, + 63, 0, 6, 33, + 65, 109, 5, 1, + 87, 109, 5, 1, + 89, 0, 2, 9, + 114, 0, 2, 9, + 115, 213,229,240, + 140, 213,229,240, + 142, 0, 2, 9, + 165, 0, 2, 9, + 166, 109, 5, 1, + 192, 109, 5, 1, + 193, 0, 6, 33, + 216, 0, 6, 33, + 217, 133,168,188, + 240, 133,168,188, + 241, 109, 5, 1, + 255, 109, 5, 1}; + +// Gradient palette "konkikyo_19_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konkikyo/tn/konkikyo-19.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( konkikyo_19_gp ) { + 0, 1, 2, 9, + 101, 1, 2, 9, + 102, 199,213,252, + 122, 199,213,252, + 126, 1, 2, 9, + 151, 1, 2, 9, + 151, 24,128,245, + 177, 24,128,245, + 178, 1, 2, 9, + 203, 1, 2, 9, + 203, 177,133, 1, + 229, 177,133, 1, + 229, 1, 2, 9, + 252, 1, 2, 9, + 255, 1, 2, 9}; + +// Gradient palette "sulz_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_22_gp ) { + 0, 247, 54, 7, + 1, 247, 54, 7, + 24, 247, 54, 7, + 25, 1, 1, 1, + 51, 1, 1, 1, + 51, 247,248,247, + 75, 247,248,247, + 75, 247, 54, 7, + 101, 247, 54, 7, + 102, 1, 1, 1, + 115, 1, 1, 1, + 115, 247, 54, 7, + 138, 247, 54, 7, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 247, 54, 7, + 179, 247, 54, 7, + 181, 247,248,247, + 202, 247,248,247, + 203, 1, 1, 1, + 228, 1, 1, 1, + 228, 247, 54, 7, + 249, 247, 54, 7, + 255, 247, 54, 7}; + +// Gradient palette "Pills_2_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/icons/tn/Pills-2.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( Pills_2_gp ) { + 0, 192,147, 11, + 127, 148,104, 59, + 255, 109, 69,155}; + +// Gradient palette "Pink_Yellow_Orange_1_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/icons/tn/Pink-Yellow-Orange-1.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Pink_Yellow_Orange_1_gp ) { + 0, 255,199, 0, + 34, 255,121, 0, + 106, 255, 63, 0, + 168, 194, 13, 6, + 255, 146, 1, 37}; +// Gradient palette "es_autumn_04_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_04.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_autumn_04_gp ) { + 0, 2, 1, 1, + 101, 27, 1, 0, + 165, 210, 22, 1, + 234, 255,166, 42, + 255, 255,166, 42}; + +// Gradient palette "es_autumn_02_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_02.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_autumn_02_gp ) { + 0, 86, 6, 1, + 127, 255,255,125, + 153, 255,255,125, + 242, 194, 96, 1, + 255, 194, 96, 1}; + +// Gradient palette "es_candide_30_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/candide/tn/es_candide_30.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte lite_light_gp[] PROGMEM = { - 0, 0, 0, 0, - 9, 1, 1, 1, - 40, 5, 5, 6, - 66, 5, 5, 6, - 101, 10, 1, 12, - 255, 0, 0, 0}; +DEFINE_PALETTE( es_candide_30_gp ) { + 0, 242,244,242, + 63, 133,255,137, + 127, 242,146,194, + 191, 104,187,245, + 252, 232,239,237, + 255, 232,239,237}; + +// Gradient palette "es_chic_16_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/chic/tn/es_chic_16.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( es_chic_16_gp ) { + 0, 4, 1, 1, + 51, 135, 99, 3, + 63, 222,248,160, + 76, 110,118, 50, + 89, 72, 55, 6, + 127, 4, 1, 1, + 165, 72, 55, 6, + 172, 90, 84, 22, + 178, 110,118, 50, + 191, 222,248,160, + 204, 135, 99, 3, + 247, 4, 1, 1, + 255, 4, 1, 1}; + +// Gradient palette "es_coffee_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/coffee/tn/es_coffee_01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. -// Gradient palette "bhw2_22_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_22.png.index.html +DEFINE_PALETTE( es_coffee_01_gp ) { + 0, 152,173,123, + 13, 152,154,106, + 25, 150,136, 91, + 63, 133, 78, 35, + 86, 112, 46, 15, + 114, 86, 15, 1, + 153, 68, 6, 1, + 178, 46, 1, 1, + 191, 31, 1, 1, + 216, 14, 1, 0, + 255, 6, 1, 0}; + +// Gradient palette "es_emerald_dragon_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_01.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Pink Plasma in SR // Size: 20 bytes of program space. -const byte red_flash_gp[] PROGMEM = { - 0, 0, 0, 0, - 99, 227, 1, 1, - 130, 249,199, 95, - 155, 227, 1, 1, - 255, 0, 0, 0}; +DEFINE_PALETTE( es_emerald_dragon_01_gp ) { + 0, 1, 1, 1, + 79, 1, 19, 7, + 130, 1, 59, 25, + 229, 28,255,255, + 255, 28,255,255}; + +// Gradient palette "es_landscape_57_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_57.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_57_gp ) { + 0, 27, 91, 0, + 89, 126,171,106, + 91, 157,199,255, + 143, 45,142,245, + 191, 3, 96,235, + 255, 1, 15, 22}; +// Gradient palette "es_landscape_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. -// Gradient palette "bhw3_40_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_40.png.index.html +DEFINE_PALETTE( es_landscape_22_gp ) { + 0, 1, 6, 1, + 38, 7, 49, 1, + 63, 21,124, 1, + 68, 173,244,252, + 127, 10,164,156, + 255, 5, 68, 66}; +// Gradient palette "es_landscape_47_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_47.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_47_gp ) { + 0, 175,125, 44, + 38, 88, 45, 3, + 58, 46, 27, 1, + 76, 20, 14, 0, + 79, 249,193,140, + 255, 121, 27, 1}; + +// Gradient palette "es_landscape_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_10.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte blink_red_gp[] PROGMEM = { +DEFINE_PALETTE( es_landscape_10_gp ) { + 0, 244,213, 55, + 24, 242,209, 53, + 51, 237,203, 51, + 63, 210,252,252, + 89, 171,225,230, + 127, 123,221,203, + 204, 25,122,144, + 255, 10, 93,115}; + +// Gradient palette "es_landscape_76_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_76.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_landscape_76_gp ) { + 0, 252,178, 82, + 127, 208, 91, 7, + 132, 153,173,188, + 191, 163,187,221, + 255, 130,191,250}; + +// Gradient palette "es_landscape_61_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_61.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_landscape_61_gp ) { + 0, 90,199, 1, + 89, 73,219, 6, + 127, 34,189, 6, + 128, 113,221, 75, + 130, 255,252,255, + 178, 64,189,255, + 255, 1,122,255}; + +// Gradient palette "es_landscape_60_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_60.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( es_landscape_60_gp ) { + 0, 161,112, 18, + 51, 130, 78, 1, + 89, 95, 59, 1, + 91, 133,151,140, + 136, 22, 92, 91, + 178, 1, 49, 52, + 242, 0, 1, 1, + 255, 0, 1, 1}; + +// Gradient palette "es_landscape_51_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_51.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_51_gp ) { + 0, 128,128,103, + 39, 165,161,144, + 76, 206,195,190, + 114, 15, 71,247, + 178, 1, 9, 71, + 255, 1, 1, 10}; + +// Gradient palette "es_landscape_06_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_06.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( es_landscape_06_gp ) { + 0, 90,199, 1, + 89, 173,244,252, + 255, 57,175,207}; + +// Gradient palette "es_ocean_breeze_049_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_049.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_049_gp ) { + 0, 184,231,250, + 76, 0,112,203, + 77, 29,168,228, + 79, 179,235,255, + 153, 64,189,255, + 255, 0,124,199}; + +// Gradient palette "es_ocean_breeze_057_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_057.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_057_gp ) { + 0, 115, 82, 49, + 76, 87, 51, 22, + 79, 249, 71, 9, + 101, 249,122, 17, + 140, 247,121, 38, + 178, 175,125, 71, + 229, 123,108, 83, + 255, 83, 97, 83}; + +// Gradient palette "es_ocean_breeze_074_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_074.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_074_gp ) { 0, 1, 1, 1, - 43, 4, 1, 11, - 76, 10, 1, 3, - 109, 161, 4, 29, - 127, 255, 86,123, - 165, 125, 16,160, - 204, 35, 13,223, - 255, 18, 2, 18}; - -// Gradient palette "bhw3_52_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_52.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Blue in SR + 101, 34, 23, 3, + 127, 53, 26, 2, + 130, 203, 65, 7, + 153, 78, 56, 8, + 191, 22, 37, 11, + 255, 1, 4, 1}; + +// Gradient palette "es_pinksplash_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. + +DEFINE_PALETTE( es_pinksplash_05_gp ) { + 0, 206, 1, 25, + 20, 192, 45, 82, + 38, 179,182,182, + 76, 206, 1, 25, + 127, 255,135,252, + 178, 206, 1, 25, + 216, 179,182,182, + 231, 192, 45, 82, + 255, 206, 1, 25}; + +// Gradient palette "es_pinksplash_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte red_shift_gp[] PROGMEM = { - 0, 31, 1, 27, - 45, 34, 1, 16, - 99, 137, 5, 9, - 132, 213,128, 10, - 175, 199, 22, 1, - 201, 199, 9, 6, - 255, 1, 0, 1}; +DEFINE_PALETTE( es_pinksplash_10_gp ) { + 0, 26, 17, 27, + 63, 184, 1, 37, + 76, 234,141,174, + 89, 148, 2, 35, + 127, 26, 17, 27, + 252, 90, 65, 89, + 255, 90, 65, 89}; + +// Gradient palette "es_vintage_56_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_56.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. -// Gradient palette "bhw4_097_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_097.png.index.html +DEFINE_PALETTE( es_vintage_56_gp ) { + 0, 220,225,221, + 51, 83, 79, 7, + 109, 25, 0, 1, + 119, 255,131, 19, + 127, 217,221,184, + 135, 255,131, 19, + 145, 25, 0, 1, + 204, 60, 46, 1, + 255, 220,225,221}; + +// Gradient palette "es_vintage_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_10.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Red in SR -// Size: 44 bytes of program space. +// Size: 16 bytes of program space. + +DEFINE_PALETTE( es_vintage_10_gp ) { + 0, 1, 3, 1, + 51, 7, 1, 1, + 127, 112, 18, 0, + 255, 206,207,182}; + +// Gradient palette "gold_yellow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/clth/tn/gold-yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( gold_yellow_gp ) { + 0, 0, 0, 0, + 94, 42, 29, 0, + 189, 255,135, 0, + 213, 255,189, 4, + 238, 255,255, 25, + 246, 255,255,103, + 255, 255,255,255}; + +// Gradient palette "radioactive_slime_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/faun/tn/radioactive-slime.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( radioactive_slime_gp ) { + 0, 0, 0, 0, + 25, 1, 4, 1, + 58, 1, 19, 1, + 76, 4, 30, 4, + 101, 17, 43, 13, + 118, 12, 69, 13, + 135, 8,100, 13, + 150, 27,146, 36, + 174, 59,199, 75, + 195, 135,195, 79, + 222, 255,189, 84, + 239, 255,221, 96, + 255, 255,255,111}; + +// Gradient palette "pastel_rainbow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/othr/tn/pastel-rainbow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( pastel_rainbow_gp ) { + 0, 0, 0, 0, + 33, 1, 2, 8, + 67, 7, 12, 45, + 88, 27, 18, 31, + 110, 67, 27, 19, + 129, 83, 38, 52, + 147, 100, 53,103, + 168, 90, 96, 93, + 189, 79,156, 83, + 206, 110,178,132, + 222, 148,203,197, + 238, 197,227,223, + 255, 255,255,255}; + +// Gradient palette "purple_sunset_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/othr/tn/purple-sunset.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( purple_sunset_gp ) { + 0, 0, 0, 0, + 31, 1, 1, 1, + 62, 1, 1, 7, + 62, 3, 2, 6, + 63, 6, 4, 5, + 88, 16, 8, 9, + 114, 31, 14, 15, + 131, 45, 22, 22, + 148, 61, 31, 31, + 152, 65, 39, 37, + 155, 69, 48, 45, + 192, 118, 86, 46, + 225, 184,135, 47, + 238, 197,161, 72, + 255, 213,187,103}; + +// Gradient palette "rainfall_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/jjg/misc/tn/rainfall.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( rainfall_gp ) { + 0, 192,118, 3, + 36, 192,118, 3, + 36, 222,118, 24, + 72, 222,118, 24, + 72, 224,209, 37, + 109, 224,209, 37, + 109, 58,159, 43, + 145, 58,159, 43, + 145, 7,133, 52, + 182, 7,133, 52, + 182, 4,118, 50, + 218, 4,118, 50, + 218, 1, 85, 8, + 255, 1, 85, 8}; + +// Gradient palette "sulz_12_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-12.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_12_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 17, 38, 14, + 45, 17, 38, 14, + 46, 1, 3, 10, + 69, 1, 3, 10, + 70, 17, 38, 14, + 91, 17, 38, 14, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 17, 38, 14, + 182, 17, 38, 14, + 183, 1, 3, 10, + 206, 1, 3, 10, + 206, 17, 38, 14, + 228, 17, 38, 14, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_10_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 117, 1,168, + 45, 117, 1,168, + 46, 1, 3, 10, + 69, 1, 3, 10, + 69, 117, 1,168, + 91, 117, 1,168, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 117, 1,168, + 182, 117, 1,168, + 183, 1, 3, 10, + 206, 1, 3, 10, + 206, 117, 1,168, + 229, 117, 1,168, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_15_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 57, 1, 1, + 45, 57, 1, 1, + 46, 1, 3, 10, + 69, 1, 3, 10, + 69, 57, 1, 1, + 90, 57, 1, 1, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 57, 1, 1, + 181, 57, 1, 1, + 183, 1, 3, 10, + 206, 1, 3, 10, + 207, 57, 1, 1, + 229, 57, 1, 1, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_21_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-21.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_21_gp ) { + 0, 247,248, 7, + 1, 247,248, 7, + 23, 247,248, 7, + 25, 1, 1, 1, + 51, 1, 1, 1, + 51, 247,248,247, + 75, 247,248,247, + 75, 247,248, 7, + 100, 247,248, 7, + 102, 1, 1, 1, + 115, 1, 1, 1, + 115, 247,248, 7, + 138, 247,248, 7, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 247,248, 7, + 179, 247,248, 7, + 181, 247,248,247, + 202, 247,248,247, + 203, 1, 1, 1, + 228, 1, 1, 1, + 229, 247,248, 7, + 249, 247,248, 7, + 255, 247,248, 7}; + +// Gradient palette "fib53_07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( fib53_07_gp ) { + 0, 98,114,102, + 2, 98,114,240, + 24, 98,114,240, + 25, 239,114,240, + 50, 239,114,240, + 50, 98,241,240, + 75, 98,241,240, + 76, 239,114,102, + 101, 239,114,102, + 102, 239,241,240, + 118, 239,241,240, + 120, 1, 1, 1, + 134, 1, 1, 1, + 135, 239,241,240, + 151, 239,241,240, + 153, 239,114,102, + 177, 239,114,102, + 179, 98,241,240, + 203, 98,241,240, + 204, 239,114,240, + 228, 239,114,240, + 229, 98,114,240, + 252, 98,114,240, + 255, 98,114,102}; + +// Gradient palette "fib53_13_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-13.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( fib53_13_gp ) { + 0, 6, 61,240, + 101, 6, 61,240, + 101, 239,241,240, + 127, 239,241,240, + 128, 1, 1, 1, + 152, 1, 1, 1, + 153, 239,241,240, + 178, 239,241,240, + 178, 6, 61,240, + 202, 6, 61,240, + 203, 239,241,240, + 229, 239,241,240, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "fib53_17_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-17.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( fib53_17_gp ) { + 0, 227,231,140, + 2, 1, 1, 1, + 12, 1, 1, 1, + 13, 73,184, 31, + 76, 73,184, 31, + 77, 1, 1, 1, + 89, 1, 1, 1, + 89, 1,121, 1, + 166, 1,121, 1, + 166, 1, 1, 1, + 179, 1, 1, 1, + 179, 1, 56, 1, + 241, 1, 56, 1, + 241, 1, 1, 1, + 252, 1, 1, 1, + 255, 227,231,140}; + +// Gradient palette "fib53_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 72 bytes of program space. + +DEFINE_PALETTE( fib53_05_gp ) { + 0, 239,241,240, + 23, 239,241,240, + 25, 239,114,102, + 51, 239,114,102, + 51, 98,114,240, + 75, 98,114,240, + 77, 239,114,240, + 99, 239,114,240, + 101, 98,114,102, + 125, 98,114,102, + 127, 98,241,240, + 152, 98,241,240, + 153, 1, 1, 1, + 178, 1, 1, 1, + 179, 98,241,240, + 204, 98,241,240, + 205, 239,241,240, + 255, 239,241,240}; + +// Gradient palette "fib53_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( fib53_18_gp ) { + 0, 73,184, 31, + 179, 73,184, 31, + 179, 1, 1, 1, + 192, 1, 1, 1, + 193, 1, 56, 1, + 205, 1, 56, 1, + 205, 239,241,240, + 216, 239,241,240, + 217, 1,121, 1, + 229, 1,121, 1, + 230, 1, 1, 1, + 243, 1, 1, 1, + 243, 227,231,140, + 255, 227,231,140}; + +// Gradient palette "fib53_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 80 bytes of program space. + +DEFINE_PALETTE( fib53_01_gp ) { + 0, 239,241,240, + 2, 6, 88, 77, + 30, 6, 88, 77, + 30, 239,241,240, + 45, 239,241,240, + 46, 73, 88, 77, + 61, 73, 88, 77, + 62, 239,241,240, + 77, 239,241,240, + 79, 6, 88, 77, + 173, 6, 88, 77, + 174, 239,241,240, + 191, 239,241,240, + 192, 73, 88, 77, + 209, 73, 88, 77, + 210, 239,241,240, + 224, 239,241,240, + 225, 6, 88, 77, + 252, 6, 88, 77, + 255, 239,241,240}; + +// Gradient palette "mccahon_16_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/mccahon/tn/mccahon-16.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. -const byte red_tide_gp[] PROGMEM = { - 0, 247, 5, 0, - 28, 255, 67, 1, - 43, 234, 88, 11, - 58, 234,176, 51, - 84, 229, 28, 1, - 114, 113, 12, 1, - 140, 255,225, 44, - 168, 113, 12, 1, - 196, 244,209, 88, - 216, 255, 28, 1, - 255, 53, 1, 1}; - -// Gradient palette "bhw4_017_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_017.png.index.html +DEFINE_PALETTE( mccahon_16_gp ) { + 0, 237, 95, 29, + 61, 247,233,190, + 63, 109, 73, 1, + 125, 247,233,190, + 127, 186, 20, 5, + 190, 247,233,190, + 191, 3, 1, 1, + 255, 237, 95, 29}; + +// Gradient palette "frizzell_09_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-09.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( frizzell_09_gp ) { + 0, 242,142, 10, + 1, 137, 19, 0, + 63, 137, 19, 0, + 65, 210,189,119, + 76, 210,189,119, + 78, 79, 25, 1, + 88, 79, 25, 1, + 89, 210,189,119, + 102, 210,189,119, + 103, 1, 5, 6, + 115, 1, 5, 6, + 115, 45, 68, 64, + 137, 45, 68, 64, + 139, 1, 5, 6, + 152, 1, 5, 6, + 153, 210,189,119, + 163, 210,189,119, + 165, 79, 25, 1, + 175, 79, 25, 1, + 178, 210,189,119, + 188, 210,189,119, + 191, 137, 19, 0, + 252, 137, 19, 0, + 255, 242,142, 10}; + +// Gradient palette "frizzell_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( frizzell_10_gp ) { + 0, 45, 68, 64, + 11, 45, 68, 64, + 11, 242,142, 10, + 25, 242,142, 10, + 25, 1, 5, 6, + 38, 1, 5, 6, + 39, 210,189,119, + 49, 210,189,119, + 49, 79, 25, 1, + 63, 79, 25, 1, + 65, 210,189,119, + 76, 210,189,119, + 77, 137, 19, 0, + 255, 137, 19, 0}; + +// Gradient palette "frizzell_12_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-12.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 40 bytes of program space. -const byte candy2_gp[] PROGMEM = { - 0, 39, 33, 34, - 25, 4, 6, 15, - 48, 49, 29, 22, - 73, 224,173, 1, - 89, 177, 35, 5, - 130, 4, 6, 15, - 163, 255,114, 6, - 186, 224,173, 1, - 211, 39, 33, 34, - 255, 1, 1, 1}; +DEFINE_PALETTE( frizzell_12_gp ) { + 0, 45, 68, 64, + 2, 210,189,119, + 24, 210,189,119, + 25, 1, 5, 6, + 126, 1, 5, 6, + 126, 137, 19, 0, + 228, 137, 19, 0, + 230, 79, 25, 1, + 253, 79, 25, 1, + 255, 242,142, 10}; + +// Gradient palette "frizzell_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 80 bytes of program space. + +DEFINE_PALETTE( frizzell_05_gp ) { + 0, 222,133, 47, + 1, 27, 17, 4, + 28, 27, 17, 4, + 28, 247,146,178, + 53, 247,146,178, + 55, 5, 1, 1, + 84, 5, 1, 1, + 84, 247,146,178, + 112, 247,146,178, + 113, 5, 1, 1, + 139, 5, 1, 1, + 140, 247,146,178, + 166, 247,146,178, + 168, 5, 1, 1, + 195, 5, 1, 1, + 196, 247,146,178, + 223, 247,146,178, + 224, 27, 17, 4, + 253, 27, 17, 4, + 255, 222,133, 47}; + +// Gradient palette "haiyan_23_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/haiyan/tn/haiyan-23.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( haiyan_23_gp ) { + 0, 36,197,164, + 122, 36,197,164, + 124, 1, 1, 1, + 135, 1, 1, 1, + 136, 239,241,240, + 177, 239,241,240, + 178, 1, 1, 1, + 204, 1, 1, 1, + 205, 84,100, 88, + 229, 84,100, 88, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "janico_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/janico/tn/janico-22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 108 bytes of program space. + +DEFINE_PALETTE( janico_22_gp ) { + 0, 112,109, 87, + 2, 126,109,115, + 37, 126,109,115, + 38, 83, 99,115, + 76, 83, 99,115, + 77, 148,127,115, + 89, 148,127,115, + 90, 112, 65, 73, + 127, 112, 65, 73, + 128, 148,127,115, + 141, 148,127,115, + 141, 4, 1, 1, + 151, 4, 1, 1, + 152, 112, 65, 53, + 165, 112, 65, 53, + 166, 4, 1, 1, + 178, 4, 1, 1, + 179, 69, 65, 53, + 202, 69, 65, 53, + 203, 4, 1, 1, + 214, 4, 1, 1, + 216, 194,225,255, + 230, 194,225,255, + 231, 4, 1, 1, + 252, 4, 1, 1, + 253, 4, 1, 1, + 255, 194,225,255}; + // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly // by name every time. const byte* const gGradientPalettes[] PROGMEM = { + ib_jul01_gp, //13-00 Jul01 + es_vintage_57_gp, + es_vintage_01_gp, + es_rivendell_15_gp, + rgi_15_gp, + retro2_16_gp, + Analogous_1_gp, + es_pinksplash_08_gp, + es_pinksplash_07_gp, + Coral_reef_gp, + + es_ocean_breeze_068_gp, + es_ocean_breeze_036_gp, + departure_gp, + es_landscape_64_gp, + es_landscape_33_gp, + rainbowsherbet_gp, + gr65_hult_gp, + gr64_hult_gp, + GMT_drywet_gp, + ib15_gp, + + Fuschia_7_gp, + es_emerald_dragon_08_gp, + lava_gp, + fire_gp, + haiyan_23_gp, + Colorfull_gp, + Magenta_Evening_gp, + Pink_Purple_gp, + Sunset_Real_gp, + es_autumn_19_gp, + + BlacK_Blue_Magenta_White_gp, + BlacK_Magenta_Red_gp, + BlacK_Red_Magenta_Yellow_gp, + Blue_Cyan_Yellow_gp, + Sunset_Yellow_gp, + cloud_gp, + fireandice_gp, + bhw2_39_gp, + rainfall_gp, + tashangel_gp, + + butterflytalker_gp, + os250k_metres_gp, + Night_Midnight_gp, + Afterdusk_gp, + BlueSky_gp, + Gold_Orange_gp, + frizzell_05_gp, + frizzell_09_gp, + frizzell_10_gp, + frizzell_12_gp, + + fib53_01_gp, + fib53_18_gp, + fib53_07_gp, + fib53_13_gp, + fib53_17_gp, + fib53_05_gp, + Analogous_02_gp, + Analogous_04a_gp, + Cyan_Orange_Stripped_gp, + Cyan_White_Green_gp, + + Wild_Orange_gp, + IKat_Radial_gp, + Citrus_gp, + Teal_Blue_gp, + Ldby_Orange_gp, + purple_orange_d07_gp, + blue_tan_d08_gp, + green_purple_d07_gp, + knoza_00_gp, + knoza_18_gp, + + calpan_18_gp, + calbayo_18_gp, + fib53_15_gp, + grindylow_15_gp, + grindylow_21_gp, + konjo_08_gp, + konjo_18_gp, + konjo_19_gp, + konkikyo_19_gp, + mccahon_16_gp, + sulz_10_gp, + sulz_12_gp, + sulz_15_gp, + sulz_21_gp, + sulz_22_gp, + Pills_2_gp, + Pink_Yellow_Orange_1_gp, + es_autumn_04_gp, + es_autumn_02_gp, + es_candide_30_gp, + es_chic_16_gp, + es_coffee_01_gp, + es_emerald_dragon_01_gp, + es_landscape_57_gp, + es_landscape_22_gp, + es_landscape_47_gp, + es_landscape_10_gp, + es_landscape_76_gp, + es_landscape_61_gp, + es_landscape_60_gp, + es_landscape_51_gp, + es_landscape_06_gp, + es_ocean_breeze_049_gp, + es_ocean_breeze_057_gp, + es_ocean_breeze_074_gp, + es_pinksplash_05_gp, + es_pinksplash_10_gp, + es_vintage_56_gp, + es_vintage_10_gp, + gold_yellow_gp, + radioactive_slime_gp, + pastel_rainbow_gp, + purple_sunset_gp, + janico_22_gp + +/* Sunset_Real_gp, //13-00 Sunset es_rivendell_15_gp, //14-01 Rivendell es_ocean_breeze_036_gp, //15-02 Breeze @@ -909,6 +2149,10 @@ const byte* const gGradientPalettes[] PROGMEM = { red_shift_gp, //68-55 Red Shift red_tide_gp, //69-56 Red Tide candy2_gp //70-57 Candy2 +*/ }; +const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); +#define GRADIENT_PALETTE_COUNT 114 + #endif diff --git a/wled00/wled.h b/wled00/wled.h index d8e40475b3..7aaa2b0135 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -12,6 +12,10 @@ //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG +#define WLED_DISABLE_MQTT +#define WLED_DISABLE_LOXONE +#define WLED_DISABLE_ALEXA +#define WLED_DISABLE_INFRARED // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From b126eb2873b732215675cf9b089d08efc35e35b7 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 5 Jul 2022 16:25:51 -0700 Subject: [PATCH 002/263] First version of externally-driven displays --- usermods/Tubes/Tubes.h | 66 +++++++++++ usermods/Tubes/beats.h | 72 ++++++++++++ usermods/Tubes/debug.h | 56 +++++++++ usermods/Tubes/options.h | 83 +++++++++++++ usermods/Tubes/pattern.h | 187 ++++++++++++++++++++++++++++++ usermods/Tubes/timer.h | 67 +++++++++++ usermods/Tubes/util.h | 16 +++ usermods/Tubes/virtual_strip.h | 206 +++++++++++++++++++++++++++++++++ wled00/FX.cpp | 46 ++------ wled00/FX.h | 156 ++++--------------------- wled00/const.h | 1 + wled00/usermods_list.cpp | 7 ++ 12 files changed, 790 insertions(+), 173 deletions(-) create mode 100644 usermods/Tubes/Tubes.h create mode 100644 usermods/Tubes/beats.h create mode 100644 usermods/Tubes/debug.h create mode 100644 usermods/Tubes/options.h create mode 100644 usermods/Tubes/pattern.h create mode 100644 usermods/Tubes/timer.h create mode 100644 usermods/Tubes/util.h create mode 100644 usermods/Tubes/virtual_strip.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h new file mode 100644 index 0000000000..2243004f91 --- /dev/null +++ b/usermods/Tubes/Tubes.h @@ -0,0 +1,66 @@ +#pragma once + +#include "wled.h" + +#include "util.h" +#include "options.h" + +// #define MASTERCONTROL + +#define MASTER_PIN 6 +#define NUM_LEDS 64 + +#define USERADIO + +#include "FX.h" + +#include "beats.h" +#include "virtual_strip.h" + +// #include "controller.h" +// #include "radio.h" +// #include "debug.h" + + +class TubesUsermod : public Usermod { + private: + BeatController beats; + // Radio radio; + // PatternController controller(NUM_LEDS, &beats, &radio); + // DebugController debug(&controller); + + void randomize(long seed) { + for (int i = 0; i < seed % 16; i++) { + randomSeed(random(INT_MAX)); + } + random16_add_entropy( random(INT_MAX) ); + } + + public: + void setup() { + // Start timing + globalTimer.setup(); + beats.setup(); + // controller.setup(0); + // debug.setup(); + } + + void loop() + { + EVERY_N_MILLISECONDS(1000) { + randomize(random(INT_MAX)); + } + + beats.update(); // ~30us + // controller.update(); // radio: 0-3000us patterns: 0-3000us lcd: ~50000us + // debug.update(); // ~25us + + // Draw after everything else is done + // controller.led_strip->update(master != NULL); // ~25us + + CRGB *external_buffer = WS2812FX::get_external_buffer(); + for (int i = 0; i < 10; i++) { + external_buffer[i] = CRGB::White; + } + } +}; \ No newline at end of file diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h new file mode 100644 index 0000000000..67643f6eef --- /dev/null +++ b/usermods/Tubes/beats.h @@ -0,0 +1,72 @@ +#pragma once + +#include "timer.h" + +#define DEFAULT_BPM 120 + +typedef uint32_t BeatFrame_24_8; // 24:8 bitwise float + +// Regulates the beat counter, running patterns at 256 "fracs" per beat +class BeatController { + public: + accum88 bpm = 0; + BeatFrame_24_8 frac; + uint32_t accum = 0; + uint32_t micros_per_frac; + + void setup() + { + globalTimer.setup(); + + // Starts in phrase 1 + this->sync(DEFAULT_BPM << 8, 0); + } + + void update() + { + globalTimer.update(); + + // Maintains an accumulator with 14 bits of precision + this->accum += globalTimer.delta_micros << 8; + while (this->accum > this->micros_per_frac) { + this->frac++; + this->accum -= this->micros_per_frac; + } + } + + void sync(accum88 bpm, BeatFrame_24_8 frac) { + accum88 last_bpm = this->bpm; + this->bpm = bpm; + this->frac = frac; + this->accum = 0; + + this->micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); + + if (last_bpm != this->bpm) + this->print_bpm(); + } + + void set_bpm(accum88 bpm) { + this->sync(bpm, this->frac); + } + + void adjust_bpm(saccum78 bpm) { + this->sync(this->bpm + bpm, this->frac); + } + + void start_phrase() { + this->frac &= -0xFFF; + this->accum = 0; + } + + void print_bpm() { + Serial.print(this->bpm >> 8); + uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(F(".")); + if (frac < 10) + Serial.print(F("0")); + Serial.print(frac); + Serial.println(F("bpm")); + } + +}; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h new file mode 100644 index 0000000000..eb31bdbab3 --- /dev/null +++ b/usermods/Tubes/debug.h @@ -0,0 +1,56 @@ +#pragma once + + +class DebugController { + public: + PatternController *controller; + LEDs *strip; + Radio *radio; + uint32_t lastPhraseTime; + uint32_t lastFrame; + + DebugController(PatternController *controller) { + this->controller = controller; + this->strip = controller->led_strip; + this->radio = controller->radio; + } + + void setup() + { + this->lastPhraseTime = globalTimer.now_micros; + this->lastFrame = (uint32_t)-1; + } + + void update() + { + EVERY_N_MILLISECONDS( 10000 ) { + Serial.print(F("Free memory: ")); + Serial.println( freeMemory() ); + } + + // Show the beat on the master OR if debugging + + if (this->controller->options.debugging) { + uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; + this->strip->leds[p1] = CRGB::White; + + uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); + this->strip->leds[p2] = CRGB::White; + + uint8_t p3 = scale8(this->controller->radio->masterTubeId, this->strip->num_leds-1); + if (p3 == p2) { + this->strip->leds[p3] = CRGB::Green; + } else { + this->strip->leds[p3] = CRGB::Yellow; + } + if (this->radio->radioRestarts) { + this->strip->leds[1] = CRGB::Red; + } + } + + if (this->radio->radioFailures && !this->radio->radioRestarts) { + this->strip->leds[0] = CRGB::Red; + } + + } +}; diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h new file mode 100644 index 0000000000..33467b9869 --- /dev/null +++ b/usermods/Tubes/options.h @@ -0,0 +1,83 @@ +#pragma once + +typedef enum SyncMode { + All=0, + SinDrift=1, + Pulse=2, + Swing=3, + SwingDrift=4, +} SyncMode; + +typedef enum Duration: uint8_t { + ShortDuration=0, + MediumDuration=10, + LongDuration=20, + ExtraLongDuration=30, +} Duration; + +typedef enum Energy: uint8_t { + LowEnergy=0, + MediumEnergy=10, + HighEnergy=20, +} Energy; + +typedef struct ControlParameters { + public: + Duration duration=MediumDuration; + Energy energy=LowEnergy; + + ControlParameters(Duration duration=MediumDuration, Energy energy=LowEnergy) { + this->duration=duration; + this->energy=energy; + }; + +} ControlParams; + +typedef enum PenMode: uint8_t { + Draw=0, + Erase=1, + Blend=2, + Invert=3, + White=4, + Black=5, + Brighten=6, + Darken=7, + Flicker=8, +} PenMode; + +typedef enum EffectMode: uint8_t { + None=0, + Glitter=1, + Bubble=2, + Beatbox1=3, + Beatbox2=4, + Spark=5, + Flash=6, +} EffectMode; + +typedef enum BeatPulse: uint8_t { + Continuous=0, + Eighth=1, + Quarter=2, + Half=4, + Beat=8, + TwoBeats=16, + Measure=32, + TwoMeasures=64, + Phrase=128, +} BeatPulse; + +class EffectParameters { + public: + EffectMode effect; + PenMode pen=Draw; + BeatPulse beat=Beat; + uint8_t chance=255; + + EffectParameters(EffectMode effect=None, PenMode pen=Draw, BeatPulse beat=Beat, uint8_t chance=255) { + this->effect=effect; + this->pen=pen; + this->beat=beat; + this->chance=chance; + }; +}; diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h new file mode 100644 index 0000000000..77fdf883ed --- /dev/null +++ b/usermods/Tubes/pattern.h @@ -0,0 +1,187 @@ +#pragma once + +#include "virtual_strip.h" + +void rainbow(VirtualStrip *strip) +{ + // FastLED's built-in rainbow generator + fill_rainbow( strip->leds, strip->num_leds, strip->hue, 3); +} + +void palette_wave(VirtualStrip *strip) +{ + // FastLED's built-in rainbow generator + uint8_t hue = strip->hue; + for (uint8_t i=0; i < strip->num_leds; i++) { + CRGB c = strip->palette_color(i, hue); + nscale8x3(c.r, c.g, c.b, sin8(hue*8)); + strip->leds[i] = c; + hue++; + } +} + +void particleTest(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Black); + fill_solid( strip->leds, 2, strip->palette_color(0, strip->hue)); +} + +void solidBlack(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Black); +} + +void solidWhite(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::White); +} + +void solidRed(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Red); +} + +void solidBlue(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Blue); +} + +void confetti(VirtualStrip *strip) +{ + strip->darken(2); + + int pos = random16(strip->num_leds); + strip->leds[pos] += strip->palette_color(random8(64), strip->hue); +} + +uint16_t random_offset = random16(); + +void biwave(VirtualStrip *strip) +{ + uint16_t l = strip->frame * 16; + l = sin16( l + random_offset ) + 32768; + + uint16_t r = strip->frame * 32; + r = cos16( r + random_offset ) + 32768; + + uint8_t p1 = scaled16to8(l, 0, strip->num_leds-1); + uint8_t p2 = scaled16to8(r, 0, strip->num_leds-1); + + if (p2 < p1) { + uint16_t t = p1; + p1 = p2; + p2 = t; + } + + strip->fill(CRGB::Black); + for (uint16_t p = p1; p <= p2; p++) { + strip->leds[p] = strip->palette_color(p*2, strip->hue*3); + } +} + +void sinelon(VirtualStrip *strip) +{ + // a colored dot sweeping back and forth, with fading trails + strip->darken(30); + + int pos = scale16(sin16( strip->frame << 5 ) + 32768, strip->num_leds-1); // beatsin16 re-implemented + strip->leds[pos] += strip->hue_color(); +} + +void bpm_palette(VirtualStrip *strip) +{ + uint8_t beat = strip->bpm_sin16(64, 255); + for (int i = 0; i < strip->num_leds; i++) { + CRGB c = strip->palette_color(i*2, strip->hue); + nscale8x3(c.r, c.g, c.b, beat-strip->hue+(i*10)); + strip->leds[i] = c; + } +} + +void bpm(VirtualStrip *strip) +{ + // colored stripes pulsing at a defined Beats-Per-Minute (BPM) + CRGBPalette16 palette = PartyColors_p; + + uint8_t beat = strip->bpm_sin16(64, 255); + for (int i = 0; i < strip->num_leds; i++) { + strip->leds[i] = ColorFromPalette(palette, strip->hue+(i*2), beat-strip->hue+(i*10)); + } +} + +void juggle(VirtualStrip *strip) +{ + // eight colored dots, weaving in and out of sync with each other + strip->darken(5); + + byte dothue = 0; + for( int i = 0; i < 8; i++) { + CRGB c = strip->palette_color(dothue + strip->hue); + // c = CHSV(dothue, 200, 255); + strip->leds[beatsin16( i+7, 0, strip->num_leds-1 )] |= c; + dothue += 32; + } +} + +uint8_t noise[MAX_VIRTUAL_LEDS]; + +void fillnoise8(uint32_t frame, uint8_t num_leds) { + uint16_t scale = 17; + uint8_t dataSmoothing = 240; + + for (int i = 0; i < num_leds; i++) { + uint8_t data = inoise8(i * scale, frame>>2); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + data = qsub8(data,16); + data = qadd8(data,scale8(data,39)); + + uint8_t olddata = noise[i]; + uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); + noise[i] = newdata; + } +} + +void drawNoise(VirtualStrip *strip) +{ + // generate noise data + fillnoise8(strip->frame >> 2, strip->num_leds); + + for(int i = 0; i < strip->num_leds; i++) { + CRGB color = strip->palette_color(noise[i], strip->hue); + strip->leds[i] = color; + } +} + +typedef struct { + BackgroundFn backgroundFn; + ControlParameters control; +} PatternDef; + + +// List of patterns to cycle through. Each is defined as a separate function below. +PatternDef gPatterns[] = { + {drawNoise, {ShortDuration}}, + {drawNoise, {ShortDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {LongDuration}}, + {drawNoise, {LongDuration}}, + {rainbow, {ShortDuration}}, + {confetti, {ShortDuration}}, + {confetti, {MediumDuration}}, + + {juggle, {ShortDuration}}, + {bpm, {ShortDuration}}, + {bpm, {MediumDuration, HighEnergy}}, + {palette_wave, {ShortDuration}}, + {palette_wave, {MediumDuration}}, + {bpm_palette, {ShortDuration}}, + {bpm_palette, {MediumDuration, HighEnergy}} +}; + +/* +*/ +const uint8_t gPatternCount = ARRAY_SIZE(gPatterns); diff --git a/usermods/Tubes/timer.h b/usermods/Tubes/timer.h new file mode 100644 index 0000000000..22b00ab482 --- /dev/null +++ b/usermods/Tubes/timer.h @@ -0,0 +1,67 @@ +#pragma once + +class GlobalTimer { + public: + uint32_t now_millis; + uint32_t now_micros; + uint32_t last_micros; + uint32_t last_millis; + uint32_t delta_micros; + uint32_t delta_millis; + + void setup() + { + this->last_millis = this->now_millis = millis(); + this->last_micros = this->now_micros = micros(); + } + + void update() + { + this->last_millis = this->now_millis; + this->now_millis = millis(); + this->delta_millis = this->now_millis - this->last_millis; + + this->last_micros = this->now_micros; + this->now_micros = micros(); + this->delta_micros = this->now_micros - this->last_micros; + } +}; + +GlobalTimer globalTimer; + + + +class Timer { + public: + uint32_t markTime; + + void start(uint32_t duration_ms) { + this->markTime = globalTimer.now_millis + duration_ms; + } + + void stop() { + this->start(0); + } + + uint32_t since_mark() { + if (globalTimer.now_millis < this->markTime) + return 0; + return globalTimer.now_millis - this->markTime; + } + + void snooze(uint32_t duration_ms) { + while (this->markTime < globalTimer.now_millis) + this->markTime += duration_ms; + } + + bool ended() { + return globalTimer.now_millis > this->markTime; + } + + bool every(uint32_t duration_ms) { + if (!this->ended()) + return 0; + this->snooze(duration_ms); + return 1; + } +}; \ No newline at end of file diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h new file mode 100644 index 0000000000..25cbc7a163 --- /dev/null +++ b/usermods/Tubes/util.h @@ -0,0 +1,16 @@ +#pragma once + +#include "wled.h" + +uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16( v, rangewidth ); + uint16_t result = lowest + scaledbeat; + return result; +} + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +#define __ESP32__ +#define USTD_OPTION_FS_FORCE_NO_FS +#include \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h new file mode 100644 index 0000000000..177f64c14f --- /dev/null +++ b/usermods/Tubes/virtual_strip.h @@ -0,0 +1,206 @@ +#pragma once + +#include "util.h" +#include "options.h" +#include "beats.h" + +#define DEFAULT_FADE_SPEED 100 +#define MAX_VIRTUAL_LEDS 150 + +class VirtualStrip; +typedef void (*BackgroundFn)(VirtualStrip *strip); + +class Background { + public: + BackgroundFn animate; + CRGBPalette16 palette; + SyncMode sync=All; +}; + +typedef enum VirtualStripFade { + Steady=0, + FadeIn=1, + FadeOut=2, + Dead=99, +} VirtualStripFade; + +BeatFrame_24_8 swing(BeatFrame_24_8 frame) { + uint16_t fr = (frame & 0x3FF); // grab 4 beats + if (fr < 256) + fr = ease8InOutApprox(fr) << 2; + else + fr = 0x3FF; + + return (frame & 0xFC00) + fr; // recompose it +} + +class VirtualStrip { + const static uint16_t DEF_BRIGHT = 192; + + public: + CRGB leds[MAX_VIRTUAL_LEDS]; + uint8_t num_leds; + uint8_t brightness; + + // Fade in/out + VirtualStripFade fade; + uint16_t fader; + uint8_t fade_speed; + + // Pattern parameters + Background background; + uint32_t frame; + uint8_t beat; + uint16_t beat16; // 8 bits of beat and 8 bits of fractional + uint8_t hue; + bool beat_pulse; + int bps = 0; + + VirtualStrip(uint8_t num_leds) + { + this->fade = Dead; + this->num_leds = num_leds; + } + + void load(Background &background, uint8_t fade_speed=DEFAULT_FADE_SPEED) + { + this->background = background; + this->fade = FadeIn; + this->fader = 0; + this->fade_speed = fade_speed; + this->brightness = DEF_BRIGHT; + } + + void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) + { + if (this->fade == Dead) + return; + this->fade = FadeOut; + this->fade_speed = fade_speed; + } + + void darken(uint8_t amount=10) + { + fadeToBlackBy( this->leds, this->num_leds, amount); + } + + void fill(CRGB crgb) + { + fill_solid( this->leds, this->num_leds, crgb); + } + + void update(BeatFrame_24_8 frame, uint8_t beat_pulse) + { + if (this->fade == Dead) + return; + + this->frame = frame; + + switch (this->background.sync) { + case All: + break; + + case SinDrift: + // Drift slightly + this->frame = frame + (beatsin16( 5 ) >> 6); + break; + + case Swing: + // Swing the beat + this->frame = swing(frame); + break; + + case SwingDrift: + // Swing the beat AND drift slightly + this->frame = swing(frame) + (beatsin16( 5 ) >> 6); + break; + + case Pulse: + // Pulsing from 30 - 210 brightness + this->brightness = scale8(beatsin8( 10 ), 180) + 30; + break; + } + this->hue = (this->frame >> 4) % 256; + this->beat = (this->frame >> 8) % 16; + this->beat_pulse = beat_pulse; + + // Animate this virtual strip + this->background.animate(this); + + switch (this->fade) { + case Steady: + case Dead: + break; + + case FadeIn: + if (65535 - this->fader < this->fade_speed) { + this->fader = 65535; + this->fade = Steady; + } else { + this->fader += this->fade_speed; + } + break; + + case FadeOut: + if (this->fader < this->fade_speed) { + this->fader = 0; + this->fade = Dead; + return; + } else { + this->fader -= this->fade_speed; + } + break; + } + } + + CRGB palette_color(uint8_t c, uint8_t offset=0) { + return ColorFromPalette( this->background.palette, c + offset ); + } + + CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { + return CHSV(this->hue + offset, saturation, value); + } + + void blend(CRGB strip[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { + if (this->fade == Dead) + return; + + brightness = scale8(this->brightness, brightness); + + for (unsigned i=0; i < num_leds; i++) { +#ifdef DOUBLED + uint8_t pos = (2*i + 1) % this->num_leds; // slope of line is fixed right now at 2:1 +#else + uint8_t pos = i; +#endif + + CRGB c = this->leds[pos]; + +#ifdef DOUBLED + CRGB c1 = this->leds[pos-1]; + CRGB c2 = this->leds[pos+1]; + nblend(c1, c, 128); + nblend(c, c2, 128); + nblend(c, c1, 128); // C is now a weighted average of the three virtual pixels +#endif + + nscale8x3(c.r, c.g, c.b, brightness); + nscale8x3(c.r, c.g, c.b, this->fader>>8); + if (overwrite) + strip[i] = c; + else + strip[i] |= c; + } + } + + uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) + { + return scaled16to8(sin16( this->frame << 7 ) + 32768, lowest, highest); + } + + uint8_t bpm_cos16( uint16_t lowest=0, uint16_t highest=65535 ) + { + return scaled16to8(cos16( this->frame << 7 ) + 32768, lowest, highest); + } + +}; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index af5ec9a9d8..00621b321b 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4228,44 +4228,16 @@ uint16_t WS2812FX::mode_aurora(void) { return FRAMETIME; } +uint16_t WS2812FX::mode_external(void) { + // uint8_t segment_id = strip.getMainSegmentId(); + uint16_t length = strip.getLengthTotal(); - - -#define MAX_VIRTUAL_LEDS 150 -uint8_t noise[MAX_VIRTUAL_LEDS]; - -void fillnoise8(uint32_t frame, uint8_t num_leds) { - uint16_t scale = 17; - uint8_t dataSmoothing = 240; - - for (int i = 0; i < num_leds; i++) { - uint8_t data = inoise8(i * scale, frame>>2); - - // The range of the inoise8 function is roughly 16-238. - // These two operations expand those values out to roughly 0..255 - data = qsub8(data,16); - data = qadd8(data,scale8(data,39)); - - uint8_t olddata = noise[i]; - uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); - noise[i] = newdata; - } -} - -uint16_t WS2812FX::mode_tubes_moise(void) { - uint16_t pixelLen = SEGLEN > MAX_VIRTUAL_LEDS ? MAX_VIRTUAL_LEDS : SEGLEN; - // uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 - // if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - // uint32_t* pixels = reinterpret_cast(SEGENV.data); - uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); - uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; - - // generate noise data - fillnoise8(now>>4, pixelLen); - - uint16_t offset = 0; - for (int i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(noise[i], true, PALETTE_SOLID_WRAP, 0)); + for (int i = 0, p = 0; i < length; i++, p++) { + if (p >= EXTERNAL_BUFFER_SIZE) { + p = 0; + } + // strip.setPixelColor(i, color_from_palette(external_buffer[p], true, PALETTE_SOLID_WRAP, 0)); + strip.setPixelColor(i, crgb_to_col(external_buffer[p])); } return FRAMETIME; diff --git a/wled00/FX.h b/wled00/FX.h index 98123f5a22..46d36d781e 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -56,6 +56,9 @@ #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME _frametime +// External buffer +#define EXTERNAL_BUFFER_SIZE 150 + /* each segment uses 52 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 @@ -239,8 +242,7 @@ #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 - -#define FX_MODE_TUBES_NOISE 118 +#define FX_MODE_EXTERNAL 118 class WS2812FX { @@ -611,7 +613,7 @@ class WS2812FX { _mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends; _mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator; _mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth; - _mode[FX_MODE_TUBES_NOISE] = &WS2812FX::mode_tubes_moise; + _mode[FX_MODE_EXTERNAL] = &WS2812FX::mode_external; _brightness = DEFAULT_BRIGHTNESS; currentPalette = CRGBPalette16(CRGB::Black); @@ -837,7 +839,7 @@ class WS2812FX { mode_blends(void), mode_tv_simulator(void), mode_dynamic_smooth(void), - mode_tubes_moise(void); + mode_external(void); private: uint32_t crgb_to_col(CRGB fastled); @@ -922,7 +924,13 @@ class WS2812FX { uint16_t transitionProgress(uint8_t tNr); + CRGB external_buffer[EXTERNAL_BUFFER_SIZE]; // 4 bytes per element + public: + static CRGB *get_external_buffer() { + return instance->external_buffer; + } + inline bool hasWhiteChannel(void) {return _hasWhiteChannel;} inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;} }; @@ -940,143 +948,19 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", "Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", "Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", -"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth", -"My Tubes" +"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth", "External!" ])====="; -// "Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", -// "Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", -// "Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", -// "Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", -// "Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", -// "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", -// "Candy2" const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", -"Forest","Rainbow","Rainbow Bands", - -"ib_jul01", -"es_vintage_57", -"es_vintage_01", -"es_rivendell_15", -"rgi_15", -"retro2_16", -"Analogous_1", -"es_pinksplash_08", -"es_pinksplash_07", -"Coral_reef", - -"es_ocean_breeze_068", -"es_ocean_breeze_036", -"departure", -"es_landscape_64", -"es_landscape_33", -"rainbowsherbet", -"gr65_hult", -"gr64_hult", -"GMT_drywet", -"ib15", - -"Fuschia_7", -"es_emerald_dragon_08", -"lava", -"fire", -"haiyan_23", -"Colorfull", -"Magenta_Evening", -"Pink_Purple", -"Sunset_Real", -"es_autumn_19", - -"BlacK_Blue_Magenta_White", -"BlacK_Magenta_Red", -"BlacK_Red_Magenta_Yellow", -"Blue_Cyan_Yellow", -"Sunset_Yellow", -"cloud", -"fireandice", -"bhw2_39", -"rainfall", -"tashangel", - -"butterflytalker", -"os250k_metres", -"Night_Midnight", -"Afterdusk", -"BlueSky", -"Gold_Orange", -"frizzell_05", -"frizzell_09", -"frizzell_10", -"frizzell_12", - -"fib53_01", -"fib53_18", -"fib53_07", -"fib53_13", -"fib53_17", -"fib53_05", -"Analogous_02", -"Analogous_04a", -"Cyan_Orange_Stripped", -"Cyan_White_Green", - -"Wild_Orange", -"IKat_Radial", -"Citrus", -"Teal_Blue", -"Ldby_Orange", -"purple_orange_d07", -"blue_tan_d08", -"green_purple_d07", -"knoza_00", -"knoza_18", - -"calpan_18", -"calbayo_18", -"fib53_15", -"grindylow_15", -"grindylow_21", -"konjo_08", -"konjo_18", -"konjo_19", -"konkikyo_19", -"mccahon_16", -"sulz_10", -"sulz_12", -"sulz_15", -"sulz_21", -"sulz_22", -"Pills_2", -"Pink_Yellow_Orange_1", -"es_autumn_04", -"es_autumn_02", -"es_candide_30", -"es_chic_16", -"es_coffee_01", -"es_emerald_dragon_01", -"es_landscape_57", -"es_landscape_22", -"es_landscape_47", -"es_landscape_10", -"es_landscape_76", -"es_landscape_61", -"es_landscape_60", -"es_landscape_51", -"es_landscape_06", -"es_ocean_breeze_049", -"es_ocean_breeze_057", -"es_ocean_breeze_074", -"es_pinksplash_05", -"es_pinksplash_10", -"es_vintage_56", -"es_vintage_10", -"gold_yellow", -"radioactive_slime", -"pastel_rainbow", -"purple_sunset", -"janico_22" +"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", +"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", +"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", +"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", +"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", +"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", +"Candy2" ])====="; #endif diff --git a/wled00/const.h b/wled00/const.h index 6b9517d97c..ac99fb7eec 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -76,6 +76,7 @@ #define USERMOD_ID_WORDCLOCK 27 //Usermod "usermod_v2_word_clock.h" #define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h" #define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h" +#define USERMOD_ID_TUBES 30 //Usermod "usermod_tubes.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 6e8f6999b9..58863c5e5f 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -128,6 +128,8 @@ #include "../usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h" #endif +#include "../usermods/Tubes/Tubes.h" + void registerUsermods() { /* @@ -243,4 +245,9 @@ void registerUsermods() #ifdef USERMOD_SI7021_MQTT_HA usermods.add(new Si7021_MQTT_HA()); #endif + + #ifdef USERMOD_TUBES + usermods.add(new TubesUsermod()); + #endif + } From 6882e841f104be418051f2b35e3342c5884aa4bd Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 6 Jul 2022 00:03:15 -0700 Subject: [PATCH 003/263] Light tubes are up and running --- usermods/Tubes/Tubes.h | 72 +++- usermods/Tubes/controller.h | 686 +++++++++++++++++++++++++++++++++ usermods/Tubes/debug.h | 4 +- usermods/Tubes/effects.h | 155 ++++++++ usermods/Tubes/global_state.h | 54 +++ usermods/Tubes/led_strip.h | 60 +++ usermods/Tubes/particle.h | 227 +++++++++++ usermods/Tubes/radio.h | 252 ++++++++++++ usermods/Tubes/virtual_strip.h | 13 - 9 files changed, 1488 insertions(+), 35 deletions(-) create mode 100644 usermods/Tubes/controller.h create mode 100644 usermods/Tubes/effects.h create mode 100644 usermods/Tubes/global_state.h create mode 100644 usermods/Tubes/led_strip.h create mode 100644 usermods/Tubes/particle.h create mode 100644 usermods/Tubes/radio.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 2243004f91..cb167cedd0 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -7,27 +7,28 @@ // #define MASTERCONTROL -#define MASTER_PIN 6 -#define NUM_LEDS 64 +#define MASTER_PIN 6 -#define USERADIO +// #define USERADIO #include "FX.h" #include "beats.h" #include "virtual_strip.h" +#include "led_strip.h" -// #include "controller.h" -// #include "radio.h" -// #include "debug.h" +#include "controller.h" +#include "radio.h" +#include "debug.h" class TubesUsermod : public Usermod { private: BeatController beats; - // Radio radio; - // PatternController controller(NUM_LEDS, &beats, &radio); - // DebugController debug(&controller); + Radio radio; + PatternController controller = PatternController(MAX_REAL_LEDS, &beats, &radio); + DebugController debug = DebugController(&controller); + int* master = NULL; /* master.h deleted */ void randomize(long seed) { for (int i = 0; i < seed % 16; i++) { @@ -41,8 +42,8 @@ class TubesUsermod : public Usermod { // Start timing globalTimer.setup(); beats.setup(); - // controller.setup(0); - // debug.setup(); + controller.setup(0); + debug.setup(); } void loop() @@ -51,16 +52,45 @@ class TubesUsermod : public Usermod { randomize(random(INT_MAX)); } - beats.update(); // ~30us - // controller.update(); // radio: 0-3000us patterns: 0-3000us lcd: ~50000us - // debug.update(); // ~25us + beats.update(); + controller.update(); + debug.update(); // Draw after everything else is done - // controller.led_strip->update(master != NULL); // ~25us - - CRGB *external_buffer = WS2812FX::get_external_buffer(); - for (int i = 0; i < 10; i++) { - external_buffer[i] = CRGB::White; - } + controller.led_strip->update(master != NULL); // ~25us } -}; \ No newline at end of file +}; + + + + +/* +LIST OF GOOD PATTERNS + +Aurora +Dynamic Smooth +Blends +Colortwinkles +Fillnoise - maybe +Fireworks +Fireworks Starburst +Flow +Gradient - maybe +Juggle - maybe +Lake +Meteor Smooth - maybe +Noise 2 +Noise 4 +Pacifica +Palette - maybe +Phased - maybe +Plasma +Ripple +Running Dual +Saw - maybe +Sinelon Dual - maybe +Tetrix - maybe +Twinklecat +Twinkleup + +*/ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h new file mode 100644 index 0000000000..01539dc63c --- /dev/null +++ b/usermods/Tubes/controller.h @@ -0,0 +1,686 @@ +#pragma once + +#include "beats.h" + +#include "pattern.h" +#include "palettes.h" +#include "effects.h" +#include "global_state.h" +#include "radio.h" + +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 144; + +const static CommandId COMMAND_UPDATE = 0x411; +const static CommandId COMMAND_NEXT = 0x321; +const static CommandId COMMAND_RESET = 0x911; +const static CommandId COMMAND_FIREWORK = 0xFFF; +const static CommandId COMMAND_HELLO = 0x000; +const static CommandId COMMAND_OPTIONS = 0x123; +const static CommandId COMMAND_BRIGHTNESS = 0x888; + + +typedef struct { + bool debugging; + uint8_t brightness; +} ControllerOptions; + +#define NEXT_PATTERN_TIME 53000 +#define NEXT_PALETTE_TIME 27000 + +#define NUM_VSTRIPS 3 + +#define DEBOUNCE_TIME 40 + +class Button { + public: + Timer debounceTimer; + uint8_t pin; + bool lastPressed = false; + + void setup(uint8_t pin) { + this->pin = pin; + pinMode(pin, INPUT_PULLUP); + this->debounceTimer.start(0); + } + + bool pressed() { + if (digitalRead(this->pin) == HIGH) { + return !this->debounceTimer.ended(); + } + + this->debounceTimer.start(DEBOUNCE_TIME); + return true; + } + + bool triggered() { + // Triggers BOTH low->high AND high->low + bool p = this->pressed(); + bool lp = this->lastPressed; + this->lastPressed = p; + return p != lp; + } +}; + +class PatternController : public MessageReceiver { + public: + const static int FRAMES_PER_SECOND = 300; // how often we animate, in frames per second + const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds + + uint8_t num_leds; + VirtualStrip *vstrips[NUM_VSTRIPS]; + uint8_t next_vstrip = 0; + bool isMaster = false; + + Timer graphicsTimer; + Timer updateTimer; + Timer slaveTimer; + +#ifdef USELCD + Lcd *lcd; +#endif + LEDs *led_strip; + BeatController *beats; + Radio *radio; + Effects *effects; + + ControllerOptions options; + char key_buffer[20] = {0}; + + Energy energy=LowEnergy; + TubeState current_state; + TubeState next_state; + + PatternController(uint8_t num_leds, BeatController *beats, Radio *radio) { + this->num_leds = num_leds; +#ifdef USELCD + this->lcd = new Lcd(); +#endif + this->led_strip = new LEDs(num_leds); + this->beats = beats; + this->radio = radio; + this->effects = new Effects(); + + for (uint8_t i=0; i < NUM_VSTRIPS; i++) { +#ifdef DOUBLED + this->vstrips[i] = new VirtualStrip(num_leds * 2 + 1); +#else + this->vstrips[i] = new VirtualStrip(num_leds); +#endif + } + + } + + void setup(bool isMaster) + { + this->isMaster = isMaster; + this->options.debugging = false; + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + +#ifdef USELCD + this->lcd->setup(); +#endif + this->set_next_pattern(0); + this->set_next_palette(0); + this->set_next_effect(0); + this->next_state.pattern_phrase = 0; + this->next_state.palette_phrase = 0; + this->next_state.effect_phrase = 0; + Serial.println(F("Patterns: ok")); + + this->radio->setup(this->isMaster); + this->radio->sendCommand(COMMAND_HELLO); + + this->slaveTimer.start(RADIO_SENDPERIOD * 3); // Assume we're a slave at first, just listen for a master. + this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to + } + + void update() + { + this->read_keys(); + + // If master has expired, clear masterId + if (this->radio->masterTubeId && this->slaveTimer.ended()) { + Serial.println(F("I have no master")); + this->radio->masterTubeId = 0; + } + + // Update patterns to the beat + this->update_beat(); + + uint16_t phrase = this->current_state.beat_frame >> 12; + if (phrase >= this->next_state.pattern_phrase) { + this->load_pattern(this->next_state); + this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + } + if (phrase >= this->next_state.palette_phrase) { + this->load_palette(this->next_state); + this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + } + if (phrase >= this->next_state.effect_phrase) { + this->load_effect(this->next_state); + this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + } + + // If alone or master, send out updates + if (!this->radio->masterTubeId and this->updateTimer.ended()) { + this->send_update(); + } + + this->radio->receiveCommands(this); + + if (this->graphicsTimer.every(REFRESH_PERIOD)) { + this->updateGraphics(); + } + +#ifdef USELCD + if (this->lcd->active) { + this->lcd->size(1); + this->lcd->write(0,56, this->current_state.beat_frame); + this->lcd->write(80,56, this->x_axis); + this->lcd->write(100,56, this->y_axis); + this->lcd->show(); + + this->lcd->update(); + } +#endif + } + + void restart_phrase() { + this->beats->start_phrase(); + this->update_beat(); + this->send_update(); + } + + void set_phrase_position(uint8_t pos) { + this->beats->sync(this->beats->bpm, (this->beats->frac & -0xFFF) + (pos<<8)); + this->update_beat(); + this->send_update(); + } + + void set_tapped_bpm(accum88 bpm, uint8_t pos=15) { + // By default, restarts at 15th beat - because this is the end of a tap + this->beats->sync(bpm, (this->beats->frac & -0xFFF) + (pos<<8)); + this->update_beat(); + this->send_update(); + } + + void update_beat() { + this->current_state.bpm = this->next_state.bpm = this->beats->bpm; + this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) + if (this->current_state.bpm >= 125>>8) + this->energy = HighEnergy; + else if (this->current_state.bpm > 120>>8) + this->energy = MediumEnergy; + else + this->energy = LowEnergy; + } + + void send_update() { + this->current_state.print(); + Serial.print(F(" ")); + + if (this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state))) { + this->radio->radioFailures = 0; + this->updateTimer.snooze(RADIO_SENDPERIOD); + } else { + // might have been a collision. Back off by a small amount determined by ID + Serial.println(F("Radio update failed")); + this->updateTimer.snooze( this->radio->tubeId & 0x7F ); + this->radio->radioFailures++; + if (this->radio->radioFailures > 100) { + this->radio->setup(this->isMaster); + this->radio->radioRestarts++; + } + } + + uint16_t phrase = this->current_state.beat_frame >> 12; + Serial.print(F(" ")); + Serial.print(this->next_state.pattern_phrase - phrase); + Serial.print(F("P ")); + Serial.print(this->next_state.palette_phrase - phrase); + Serial.print(F("C ")); + Serial.print(this->next_state.effect_phrase - phrase); + Serial.print(F("E: ")); + this->next_state.print(); + Serial.print(F(" ")); + this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + Serial.println(); + } + + void background_changed() { + this->update_background(); + this->current_state.print(); + Serial.println(); + } + + void load_pattern(TubeState &tube_state) { + if (this->current_state.pattern_id == tube_state.pattern_id + && this->current_state.pattern_sync_id == tube_state.pattern_sync_id) + return; + + this->current_state.pattern_phrase = tube_state.pattern_phrase; + this->current_state.pattern_id = tube_state.pattern_id % gPatternCount; + this->current_state.pattern_sync_id = tube_state.pattern_sync_id; + + Serial.print(F("Change pattern ")); + this->background_changed(); + } + + uint16_t set_next_pattern(uint16_t phrase) { + uint8_t pattern_id = random8(gPatternCount); + PatternDef def = gPatterns[pattern_id]; + if (def.control.energy > this->energy) { + pattern_id = 0; + def = gPatterns[0]; + } + + this->next_state.pattern_id = pattern_id; + this->next_state.pattern_sync_id = this->randomSyncMode(); + + switch (def.control.duration) { + case ShortDuration: return random8(5,15); + case MediumDuration: return random8(15,25); + case LongDuration: return random8(35,45); + case ExtraLongDuration: return random8(70, 100); + } + return 5; + } + + void load_palette(TubeState &tube_state) { + if (this->current_state.palette_id == tube_state.palette_id) + return; + + this->current_state.palette_phrase = tube_state.palette_phrase; + this->_load_palette(tube_state.palette_id); + } + + void _load_palette(uint8_t palette_id) { + this->current_state.palette_id = palette_id % gGradientPaletteCount; + + Serial.print(F("Change palette")); + this->background_changed(); + } + + uint16_t set_next_palette(uint16_t phrase) { + this->next_state.palette_id = random8(gGradientPaletteCount); + return random8(4,40); + } + + void load_effect(TubeState &tube_state) { + if (this->current_state.effect_params.effect == tube_state.effect_params.effect && + this->current_state.effect_params.pen == tube_state.effect_params.pen && + this->current_state.effect_params.chance == tube_state.effect_params.chance) + return; + + this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; + this->_load_effect(tube_state.effect_params); + } + + void _load_effect(EffectParameters params) { + this->current_state.effect_params = params; + + Serial.print(F("Change effect ")); + this->current_state.print(); + Serial.println(); + + this->effects->load(this->current_state.effect_params); + } + + uint16_t set_next_effect(uint16_t phrase) { + EffectDef def = gEffects[random8(gEffectCount)]; + if (def.control.energy > this->energy) + def = gEffects[0]; + + this->next_state.effect_params = def.params; + + switch (def.control.duration) { + case ShortDuration: return 3; + case MediumDuration: return 6; + case LongDuration: return 10; + case ExtraLongDuration: return 20; + } + return 1; + } + + void update_background() { + Background background; + background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; + background.palette = gGradientPalettes[this->current_state.palette_id]; + background.sync = (SyncMode)this->current_state.pattern_sync_id; + + // re-use virtual strips to prevent heap fragmentation + for (uint8_t i = 0; i < NUM_VSTRIPS; i++) { + this->vstrips[i]->fadeOut(); + } + this->vstrips[this->next_vstrip]->load(background); + this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; + } + + void optionsChanged() { + if (this->isMaster) { + this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); + } + } + + void setBrightness(uint8_t brightness) { + Serial.print(F("brightness ")); + Serial.println(brightness); + + this->options.brightness = brightness; + this->optionsChanged(); + } + + void setDebugging(bool debugging) { + Serial.print(F("debugging ")); + Serial.println(debugging); + + this->options.debugging = debugging; + this->optionsChanged(); + } + + SyncMode randomSyncMode() { + uint8_t r = random8(128); + if (r < 40) + return SinDrift; + if (r < 65) + return Pulse; + if (r < 72) + return Swing; + if (r < 84) + return SwingDrift; + return All; + } + + void updateGraphics() { + static BeatFrame_24_8 lastFrame = 0; + BeatFrame_24_8 beat_frame = this->current_state.beat_frame; + + uint8_t beat_pulse = 0; + for (int i = 0; i < 8; i++) { + if ( (beat_frame >> (5+i)) != (lastFrame >> (5+i))) + beat_pulse |= 1<vstrips[i]; + if (vstrip->fade == Dead) + continue; + + // Remember the first strip + if (first_strip == NULL) + first_strip = vstrip; + + vstrip->update(beat_frame, beat_pulse); + vstrip->blend(this->led_strip->leds, this->led_strip->num_leds, this->options.brightness, vstrip == first_strip); + } + + this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); + this->effects->draw(this->led_strip->leds, this->num_leds); + } + + virtual void acknowledge() { + addFlash(); + } + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + if (fromId) { + Serial.print(F("From ")); + Serial.print(fromId); + Serial.print(F(": ")); + } + + switch (command) { + case COMMAND_FIREWORK: + Serial.print(F("fireworks")); + this->acknowledge(); + return; + + case COMMAND_RESET: + Serial.print(F("reset")); + return; + + case COMMAND_BRIGHTNESS: { + uint8_t *bright = (uint8_t *)data; + this->setBrightness(*bright); + return; + } + + case COMMAND_HELLO: + Serial.print(F("hello")); + this->updateTimer.stop(); + return; + + case COMMAND_OPTIONS: { + Serial.print(F("options")); + memcpy(&this->options, data, sizeof(this->options)); + return; + } + + case COMMAND_NEXT: { + Serial.print(F(" next ")); + if (fromId < this->radio->masterTubeId) { + Serial.print(F(" (ignoring)")); + return; + } + + memcpy(&this->next_state, data, sizeof(TubeState)); + this->next_state.print(); + Serial.print(F(" (obeying)")); + return; + } + + case COMMAND_UPDATE: { + Serial.print(F(" update ")); + if (fromId < this->radio->masterTubeId) { + Serial.print(F(" (ignoring)")); + return; + } + + TubeState state; + memcpy(&state, data, sizeof(TubeState)); + state.print(); + Serial.print(F(" (obeying)")); + + // Track the last time we received a message from our master + this->slaveTimer.start(RADIO_SENDPERIOD * 8); + + // Catch up to this state + this->load_pattern(state); + this->load_palette(state); + this->load_effect(state); + this->beats->sync(state.bpm, state.beat_frame); + return; + } + } + + Serial.print(F("UNKNOWN ")); + Serial.print(command, HEX); + } + + void read_keys() { + if (!Serial.available()) + return; + + char c = Serial.read(); + char *k = this->key_buffer; + uint8_t max = sizeof(this->key_buffer); + for (uint8_t i=0; *k && (i < max-1); i++) { + k++; + } + if (c == 10) { + this->keyboard_command(this->key_buffer); + this->key_buffer[0] = 0; + } else { + *k++ = c; + *k = 0; + } + } + + accum88 parse_number(char *s) { + uint16_t n=0, d=0; + + while (*s == ' ') + s++; + while (*s) { + if (*s < '0' || *s > '9') + break; + n = n*10 + (*s++ - '0'); + } + n = n << 8; + + if (*s == '.') { + uint16_t div = 1; + s++; + while (*s) { + if (*s < '0' || *s > '9') + break; + d = d*10 + (*s++ - '0'); + div *= 10; + } + d = (d << 8) / div; + } + return n+d; + } + + void keyboard_command(char *command) { + uint8_t b; + accum88 arg = this->parse_number(command+1); + + switch (command[0]) { + case 'f': + this->radio->sendCommandFrom(255, COMMAND_FIREWORK, NULL, 0); + this->onCommand(0, COMMAND_FIREWORK, NULL); + Serial.println(); + break; + + case 'i': + this->radio->resetId(arg >> 8); + break; + + case 'd': + this->setDebugging(!this->options.debugging); + break; + + case '-': + b = this->options.brightness; + while (*command++ == '-') + b -= 5; + this->setBrightness(b - 5); + break; + case '+': + b = this->options.brightness; + while (*command++ == '+') + b += 5; + this->setBrightness(b + 5); + return; + case 'l': + if (arg < 5*256) { + Serial.println(F("nope")); + return; + } + this->setBrightness(arg >> 8); + return; + + case 'b': + if (arg < 60*256) { + Serial.println(F("nope")); + return; + } + this->beats->set_bpm(arg); + this->update_beat(); + this->send_update(); + return; + + case 's': + this->beats->start_phrase(); + this->update_beat(); + this->send_update(); + return; + + case 'n': + this->force_next(); + return; + + case 'p': + this->next_state.pattern_phrase = 0; + this->next_state.pattern_id = arg >> 8; + this->next_state.pattern_sync_id = All; + this->update_next(); + return; + + case 'm': + this->next_state.pattern_phrase = 0; + this->next_state.pattern_id = this->current_state.pattern_id; + this->next_state.pattern_sync_id = arg >> 8; + this->update_next(); + return; + + case 'c': + this->next_state.palette_phrase = 0; + this->next_state.palette_id = arg >> 8; + this->update_next(); + return; + + case 'e': + this->next_state.effect_phrase = 0; + this->next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; + this->update_next(); + return; + + case '%': + this->next_state.effect_phrase = 0; + this->next_state.effect_params = this->current_state.effect_params; + this->next_state.effect_params.chance = arg; + this->update_next(); + return; + + case 'h': + // Pretend to receive a HELLO + this->onCommand(0, COMMAND_HELLO, NULL); + Serial.println(); + return; + + case 'g': + /* PARTICLES + for (int i=0; i< 10; i++) + addGlitter(); + */ + break; + + case '?': + Serial.println(F("b###.# - set bpm")); + Serial.println(F("s - start phrase")); + Serial.println(); + Serial.println(F("p### - patterns")); + Serial.println(F("m### - sync mode")); + Serial.println(F("c### - colors")); + Serial.println(F("e### - effects")); + Serial.println(); + Serial.println(F("i### - set ID")); + Serial.println(F("d - toggle debugging")); + Serial.println(F("l### - brightness")); + } + } + + void force_next() { + uint16_t phrase = this->current_state.beat_frame >> 12; + uint16_t next_phrase = min(this->next_state.pattern_phrase, min(this->next_state.palette_phrase, this->next_state.effect_phrase)) - phrase; + this->next_state.pattern_phrase -= next_phrase; + this->next_state.palette_phrase -= next_phrase; + this->next_state.effect_phrase -= next_phrase; + this->update_next(); + } + + void update_next() { + this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + } + +}; + + + +// What's interesting? +// c53 - clouds +// m4 - swing drift diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index eb31bdbab3..8f4717ab89 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,5 +1,8 @@ #pragma once +#include "controller.h" +#include "radio.h" + class DebugController { public: @@ -51,6 +54,5 @@ class DebugController { if (this->radio->radioFailures && !this->radio->radioRestarts) { this->strip->leds[0] = CRGB::Red; } - } }; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h new file mode 100644 index 0000000000..f4ad7532c2 --- /dev/null +++ b/usermods/Tubes/effects.h @@ -0,0 +1,155 @@ +#pragma once + +#include "util.h" +#include "particle.h" +#include "virtual_strip.h" + +void addGlitter(CRGB color=CRGB::White, PenMode pen=Draw) +{ + addParticle(new Particle(random16(), color, pen, 128)); +} + +void addSpark(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 64); + uint8_t r = random8(); + if (r > 128) + particle->velocity = r; + else + particle->velocity = -(128 + r); + addParticle(particle); +} + +void addBeatbox(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 256, drawBeatbox); + addParticle(particle); +} + +void addBubble(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 1024, drawPop); + particle->velocity = random16(0, 40) - 20; + addParticle(particle); +} + +void addFlash(CRGB color=CRGB::Blue, PenMode pen=Draw) +{ + addParticle(new Particle(random16(), color, pen, 256, drawFlash)); +} + +void addDrop(CRGB color, PenMode pen=Draw) +{ + Particle *particle = new Particle(65535, color, pen, 360); + particle->velocity = -500; + particle->gravity = -10; + addParticle(particle); +} + +class Effects { + public: + EffectMode effect=None; + PenMode pen=Draw; + BeatPulse beat; + uint8_t chance; + + void load(EffectParameters ¶ms) { + this->effect = params.effect; + this->pen = params.pen; + this->beat = params.beat; + this->chance = params.chance; + } + + void update(VirtualStrip *strip, BeatFrame_24_8 beat_frame, BeatPulse beat_pulse) { + if (!this->beat || beat_pulse & this->beat) { + + if (random8() <= this->chance) { + CRGB color = strip->palette_color(random8()); + + switch (this->effect) { + case None: + break; + + case Glitter: + addGlitter(color, this->pen); + break; + + case Beatbox1: + case Beatbox2: + addBeatbox(color, this->pen); + if (this->effect == Beatbox2) + addBeatbox(color, this->pen); + break; + + case Bubble: + addBubble(color, this->pen); + break; + + case Spark: + addSpark(color, this->pen); + break; + + case Flash: + addFlash(CRGB::White, this->pen); + break; + } + } + } + + this->animate(beat_frame, beat_pulse); + } + + void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { + unsigned int len = 0; /* PARTICLES particles.length(); */ + for (unsigned i=len; i > 0; i--) { + Particle *particle = particles[i-1]; + + particle->update(frame); + if (particle->age > particle->lifetime) { + delete particle; + /* PARTICLES particles.erase(i-1); */ + continue; + } + } + } + + void draw(CRGB strip[], uint8_t num_leds) { + uint8_t len = 0; /* PARTICLES particles.length(); */ + for (uint8_t i=0; idrawFn(particle, strip, num_leds); + } + } + +}; + + +typedef struct { + EffectParameters params; + ControlParameters control; +} EffectDef; + + +static const EffectDef gEffects[] = { + {{None}, {LongDuration}}, + {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, + {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, + {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, + {{Flash, Brighten, Phrase}, {MediumDuration, HighEnergy}}, + {{Flash, Darken, Measure}, {ShortDuration, LowEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Glitter, Brighten, Eighth, 80}, {MediumDuration, MediumEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {MediumDuration, HighEnergy}}, + {{Glitter, Darken, Eighth, 40}, {MediumDuration, LowEnergy}}, + + {{Glitter, Draw, Eighth, 10}, {LongDuration, LowEnergy}}, + {{Glitter, Draw, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Glitter, Invert, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Beatbox2, Black}, {MediumDuration, LowEnergy}}, + {{Beatbox2, Draw}, {ShortDuration, HighEnergy}}, + {{Bubble, Darken}, {MediumDuration, LowEnergy}}, + {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, + {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, +}; +const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h new file mode 100644 index 0000000000..a23a769aae --- /dev/null +++ b/usermods/Tubes/global_state.h @@ -0,0 +1,54 @@ +#pragma once + +#include "wled.h" +#include "beats.h" +#include "effects.h" + + +class TubeState { + public: + // Global clock: frames are defined as 1/64th of a beat + accum88 bpm = 0; // BPM in high 8 bits, fraction in low 8 bits + BeatFrame_24_8 beat_frame = 0; // current beat (24 bits) and fractional beat (8bits) + + uint16_t pattern_phrase; + uint8_t pattern_id; + uint8_t pattern_sync_id; + + uint16_t palette_phrase; + uint8_t palette_id; + + uint16_t effect_phrase; + EffectParameters effect_params; + + void print() { + uint16_t phrase = this->beat_frame >> 12; + Serial.print(F("[")); + Serial.print(phrase); + Serial.print(F(".")); + Serial.print((this->beat_frame >> 8) % 16); + Serial.print(F(" P")); + Serial.print(this->pattern_id); + Serial.print(F(",")); + Serial.print(this->pattern_sync_id); + Serial.print(F(" C")); + Serial.print(this->palette_id); + Serial.print(F(" E")); + Serial.print(this->effect_params.effect); + Serial.print(F(",")); + Serial.print(this->effect_params.pen); + Serial.print(F(",")); + Serial.print(this->effect_params.beat); + Serial.print(F(",")); + Serial.print(this->effect_params.chance); + Serial.print(F(" ")); + Serial.print(this->bpm >> 8); + uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(F(".")); + if (frac < 10) + Serial.print(F("0")); + Serial.print(frac); + Serial.print(F("bpm]")); + } + +}; diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h new file mode 100644 index 0000000000..ddbca2fd4b --- /dev/null +++ b/usermods/Tubes/led_strip.h @@ -0,0 +1,60 @@ +#pragma once + +#define USE_WLED +#include "wled.h" + +#define MAX_REAL_LEDS 64 + + +class LEDs { + public: + CRGB leds[MAX_REAL_LEDS]; + // CRGB led_array[MAX_REAL_LEDS]; + + const static int FRAMES_PER_SECOND = 300; // how often we refresh the strip, in frames per second + const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we refresh the strip, in milliseconds + int num_leds; + + uint16_t fps = 0; + + LEDs(int num_leds=MAX_REAL_LEDS) { + this->num_leds = num_leds; + } + + void setup() { + Serial.println((char *)F("LEDs: ok")); + } + + void reverse() { + for (int i=1; i<8; i++) { + CRGB c = this->leds[i]; + this->leds[i] = this->leds[16-i]; + this->leds[16-i] = c; + } + } + + void show() { + CRGB *external_buffer = WS2812FX::get_external_buffer(); + for (int i = 0; i < num_leds; i++) { + external_buffer[i] = leds[i]; + } + } + + void update(bool reverse=false) { + EVERY_N_MILLISECONDS( this->REFRESH_PERIOD ) { + // Update the LEDs + if (reverse) + this->reverse(); + show(); + this->fps++; + } + + EVERY_N_MILLISECONDS( 1000 ) { + if (this->fps < (FRAMES_PER_SECOND - 30)) { + Serial.print(this->fps); + Serial.println((char *)F(" fps!")); + } + this->fps = 0; + } + } +}; diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h new file mode 100644 index 0000000000..d49f10a1bb --- /dev/null +++ b/usermods/Tubes/particle.h @@ -0,0 +1,227 @@ +#pragma once + +#include "wled.h" +#include "beats.h" +#include "options.h" +// #include "ustd.h" + +#define MAX_PARTICLES 20 +#undef PARTICLE_PALETTES + +class Particle; + +typedef void (*ParticleFn)(Particle *particle, CRGB strip[], uint8_t num_leds); + +extern void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds); +extern void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds); + + +class Particle { + public: + BeatFrame_24_8 born; + BeatFrame_24_8 lifetime; + BeatFrame_24_8 age; + + uint16_t position = 0; + int16_t velocity = 0; + int16_t gravity = 0; + void (*die_fn)(Particle *particle) = NULL; + PenMode pen = Draw; + +#ifdef PARTICLE_PALETTES + CRGBPalette16 palette; // 48 bytes per particle!? +#endif + + CRGB color; + uint16_t brightness; + ParticleFn drawFn; + + Particle(uint16_t position, CRGB color=CRGB::White, PenMode pen=Draw, uint32_t lifetime=20000, ParticleFn drawFn=drawPoint) + { + this->age = 0; + this->position = position; + this->color = color; + this->pen = pen; + this->lifetime = lifetime; + this->brightness = (192<<8); + this->drawFn = drawFn; + } + + void update(BeatFrame_24_8 frame) + { + this->age = frame - this->born; + this->position = this->udelta16(this->position, this->velocity); + this->velocity = this->delta16(this->velocity, this->gravity); + } + + uint16_t age_frac16(BeatFrame_24_8 age) + { + if (age >= this->lifetime) + return 65535; + uint32_t a = age * 65536; + return a / this->lifetime; + } + + uint16_t udelta16(uint16_t x, int16_t dx) + { + if (dx > 0 && 65535-x < dx) + return 65335; + if (dx < 0 && x < -dx) + return 0; + return x + dx; + } + + int16_t delta16(int16_t x, int16_t dx) + { + if (dx > 0 && 32767-x < dx) + return 32767; + if (dx < 0 && x < -32767 - dx) + return -32767; + return x + dx; + } + + CRGB color_at(uint16_t age_frac) { + // Particles get dimmer with age + uint8_t a = age_frac >> 8; + uint8_t brightness = scale8((uint8_t)(this->brightness>>8), 255-a); + +#ifdef PARTICLE_PALETTES + // a black pattern actually means to use the current palette + if (this->color == CRGB(0,0,0)) + return ColorFromPalette(this->palette, a, brightness); +#endif + + uint8_t r = scale8(this->color.r, brightness); + uint8_t g = scale8(this->color.g, brightness); + uint8_t b = scale8(this->color.b, brightness); + return CRGB(r,g,b); + } + + void draw_with_pen(CRGB strip[], int pos, CRGB color) { + CRGB new_color; + + switch (this->pen) { + case Draw: + strip[pos] = color; + break; + + case Blend: + strip[pos] |= color; + break; + + case Erase: + strip[pos] &= color; + break; + + case Invert: + strip[pos] = -strip[pos]; + break; + + case Brighten: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + strip[pos] += new_color; + break; + } + + case Darken: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + strip[pos] -= new_color; + break; + } + + case Flicker: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + if (millis() % 2) + strip[pos] -= new_color; + else + strip[pos] += new_color; + break; + } + + case White: + strip[pos] = CRGB::White; + break; + + case Black: + strip[pos] = CRGB::Black; + break; + + } + } + +}; + +Particle* particles[5]; +/* PARTICLES +ustd::array particles = ustd::array(5); +*/ +BeatFrame_24_8 particle_beat_frame; + +void addParticle(Particle *particle) { + particle->born = particle_beat_frame; + /* PARTICLES + particles.add(particle); + if (particles.length() > MAX_PARTICLES) { + Particle *old_particle = particles[0]; + delete old_particle; + particles.erase(0); + } + */ +} + + +void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + for (int pos = 0; pos < num_leds; pos++) { + particle->draw_with_pen(strip, pos, c); + } +} + +void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + + uint16_t pos = scale16(particle->position, num_leds-1); + particle->draw_with_pen(strip, pos, c); +} + +void drawRadius(Particle *particle, CRGB strip[], uint8_t num_leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + for (int i = 0; i < radius; i++) { + uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; + nscale8(&c, 1, bright); + + uint8_t y = pos - i; + if (y >= 0 && y < num_leds) + particle->draw_with_pen(strip, y, c); + + if (i == 0) + continue; + + y = pos + i; + if (y >= 0 && y < num_leds) + particle->draw_with_pen(strip, y, c); + } +} + +void drawPop(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + uint16_t pos = scale16(particle->position, num_leds-1); + uint8_t radius = scale16((sin16(age_frac/2) - 32768) * 2, 8); + + drawRadius(particle, strip, num_leds, pos, radius, c); +} + +void drawBeatbox(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + uint16_t pos = scale16(particle->position, num_leds-1); + uint8_t radius = 5; + + drawRadius(particle, strip, num_leds, pos, radius, c, false); +} + diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h new file mode 100644 index 0000000000..86e3883c0c --- /dev/null +++ b/usermods/Tubes/radio.h @@ -0,0 +1,252 @@ +#ifndef RADIO_H +#define RADIO_H + +#include +#include + +#define RADIO_VERSION 1 + +#ifdef USERADIO +NRFLite _radio(Serial); +#endif + +const static uint8_t RADIO_TX_ID = 0; // Radio ID for both trans +const static uint8_t RADIO_RX_ID = 0; // Radio ID for both recv + +const static uint8_t PIN_RADIO_CE = 9; // hardware pins +const static uint8_t PIN_RADIO_CSN = 10; // hardware pins +const static uint8_t PIN_RADIO_MOSI = 11; // hardware pins +const static uint8_t PIN_RADIO_MISO = 12; // hardware pins +const static uint8_t PIN_RADIO_SCK = 13; // hardware pins + +#define RADIO_BITRATE NRFLite::BITRATE1MBPS // { BITRATE2MBPS, BITRATE1MBPS, BITRATE250KBPS } +#define RADIO_CHANNEL 100 + RADIO_VERSION // Channel hop with each version +#define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec + +class Radio; + +typedef uint16_t CommandId; +typedef uint8_t TubeId; + +#define MESSAGE_DATA_MAX_SIZE 25 +typedef struct { + CommandId command; + TubeId tubeId; + TubeId relayId; + byte data[MESSAGE_DATA_MAX_SIZE]; + uint16_t crc = 0; +} RadioMessage; + +class MessageReceiver { + public: + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + +uint16_t calculate_crc( byte *data, byte len ) { + + const unsigned long crc_table[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c + }; + + unsigned long crc = ~0L; + + for ( unsigned int index = 0 ; index < len ; ++index ) { + crc = crc_table[( crc ^ data[index] ) & 0x0f] ^ (crc >> 4); + crc = crc_table[( crc ^ ( data[index] >> 4 )) & 0x0f] ^ (crc >> 4); + crc = ~crc; + } + return crc & 65535; +} + +uint8_t newTubeId() { + return random(10, 250); // Leave room for master +} + +void printMessageData(RadioMessage &message, int size) { + Serial.print(sizeof(message.data)); + Serial.print(F(":")); + for (unsigned int i = 0; i < sizeof(message.data); i++) { + if (message.data[i] < 16) + Serial.print(F("0")); + Serial.print(message.data[i], HEX); + Serial.print(F(" ")); + } + Serial.print(F("[")); + Serial.print(size); + Serial.print(F("] ")); +} + +class Radio { + public: + bool alive = false; // true if radio booted up + bool reported_no_radio = false; + TubeId tubeId = 0; + TubeId masterTubeId = 0; + + unsigned long radioFailures = 0; + unsigned long radioRestarts = 0; + + void setup(bool isMaster) { + if (isMaster) + this->resetId(254); + else + this->resetId(); + +#ifdef USERADIO +#ifdef IS_TEENSY + SPI.setSCK(PIN_RADIO_SCK); + SPI.setMOSI(PIN_RADIO_MOSI); + SPI.setMISO(PIN_RADIO_MISO); +#endif + SPI.begin(); + + this->reported_no_radio = false; + if (_radio.init(RADIO_RX_ID, PIN_RADIO_CE, PIN_RADIO_CSN, RADIO_BITRATE, RADIO_CHANNEL)) { + this->alive = true; + } + Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); + + // Start the radio, but mute & listen for a bit +#endif + } + + void resetId(uint8_t id=0) { + if (id == 0) + id = newTubeId(); + this->tubeId = id; + Serial.print(F("My ID is ")); + Serial.println(this->tubeId); + + if (this->tubeId > this->masterTubeId) + this->masterTubeId = 0; + } + + bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + { + return this->sendCommandFrom(this->tubeId, command, data, size, relayId); + } + + bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + { +#ifndef USERADIO + return true; +#endif + + bool sent = 0; + if (!this->alive) + return sent; + +#ifdef USERADIO + RadioMessage message; + if (size > sizeof(message.data)) { + Serial.println(F("Too big to send")); + return 0; + } + + message.tubeId = id; + message.relayId = relayId; + message.command = command + (RADIO_VERSION << 12); + memset(message.data, 0, sizeof(message.data)); + memcpy(message.data, data, size); + uint16_t crc = calculate_crc(message.data, sizeof(message.data)); + message.crc = crc; + + Serial.print(F("[")); + Serial.print(message.tubeId); + Serial.print(F(": ")); + Serial.print(message.command, HEX); + + sent = _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); + Serial.print(sent ? F(" ok] ") : F(" failed] ")); +#endif + + return sent; + } + + void receiveCommands(MessageReceiver *receiver) + { +#ifdef USERADIO + RadioMessage message; + + if (!this->alive && !this->reported_no_radio) + { + Serial.println(F("No radio")); + this->reported_no_radio = true; + return; + } + + // check for incoming data + while (_radio.hasData()) + { + _radio.readData(&message); + + // Messages must be from a tube with the current version + if ((message.command>>12) != RADIO_VERSION) + return; + + // Ignore relayed messages if we already have a master + if (message.relayId && message.relayId <= this->masterTubeId) + return; + + // Filter out corrupt messages + unsigned long crc = calculate_crc(message.data, sizeof(message.data)); + if (crc != message.crc) { + // Corrupt packet... ignore it. + Serial.print(F("Invalid CRC: ")); + Serial.print(message.crc); + Serial.print(F(" should be ")); + Serial.println(crc); + continue; + } + + // If we detect an ID collision, fix it by choosing a new random one + while (message.tubeId == this->tubeId) { + Serial.print(F("ID collision!")); + this->resetId(); + } + + // Ignore messages from a lower ID + if (message.tubeId < this->tubeId) { + // Don't need to be noisy about relayed messages + if (message.relayId == 0) { + Serial.print(F("Ignoring message from ")); + Serial.println(message.tubeId); + } + return; + } + + if (message.tubeId != 255 && message.tubeId > this->masterTubeId) { + // Found a new master! + this->masterTubeId = message.tubeId; + Serial.print(F("All hail new master ")); + Serial.println(this->masterTubeId); + } + + // Process the command + receiver->onCommand(message.tubeId, message.command & 0xFFF, message.data); + + // Occcasionally relay commands - more frequently if higher ID + uint8_t r = random8(); + if ((r % 3 == 0) && r < this->tubeId) { + Serial.print(F(" (relaying as ")); + Serial.print(this->tubeId); + Serial.print(F(")")); + message.relayId = message.tubeId; + message.tubeId = this->tubeId; + _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); + } + + Serial.println(); + } +#endif + } + +}; + +#endif diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 177f64c14f..dfbf76e9d2 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -168,22 +168,9 @@ class VirtualStrip { brightness = scale8(this->brightness, brightness); for (unsigned i=0; i < num_leds; i++) { -#ifdef DOUBLED - uint8_t pos = (2*i + 1) % this->num_leds; // slope of line is fixed right now at 2:1 -#else uint8_t pos = i; -#endif - CRGB c = this->leds[pos]; -#ifdef DOUBLED - CRGB c1 = this->leds[pos-1]; - CRGB c2 = this->leds[pos+1]; - nblend(c1, c, 128); - nblend(c, c2, 128); - nblend(c, c1, 128); // C is now a weighted average of the three virtual pixels -#endif - nscale8x3(c.r, c.g, c.b, brightness); nscale8x3(c.r, c.g, c.b, this->fader>>8); if (overwrite) From 59e4c4f2febacaee9f0a40734f635ec36d8be6af Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 13 Jul 2022 00:56:32 -0700 Subject: [PATCH 004/263] Re-enable particles; fix misc issues --- usermods/Tubes/Tubes.h | 34 ++++++++++++++++--------------- usermods/Tubes/controller.h | 8 ++++---- usermods/Tubes/effects.h | 7 +++---- usermods/Tubes/particle.h | 37 +++++++++++++++++++++------------- usermods/Tubes/radio.h | 2 +- usermods/Tubes/virtual_strip.h | 3 ++- 6 files changed, 51 insertions(+), 40 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index cb167cedd0..ccc87488b1 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -30,15 +30,15 @@ class TubesUsermod : public Usermod { DebugController debug = DebugController(&controller); int* master = NULL; /* master.h deleted */ - void randomize(long seed) { - for (int i = 0; i < seed % 16; i++) { - randomSeed(random(INT_MAX)); - } - random16_add_entropy( random(INT_MAX) ); + void randomize() { + randomSeed(esp_random()); + random16_add_entropy(esp_random()); } public: void setup() { + randomize(); + // Start timing globalTimer.setup(); beats.setup(); @@ -48,8 +48,8 @@ class TubesUsermod : public Usermod { void loop() { - EVERY_N_MILLISECONDS(1000) { - randomize(random(INT_MAX)); + EVERY_N_MILLISECONDS(10000) { + randomize(); } beats.update(); @@ -71,26 +71,28 @@ Aurora Dynamic Smooth Blends Colortwinkles -Fillnoise - maybe Fireworks Fireworks Starburst Flow -Gradient - maybe -Juggle - maybe Lake -Meteor Smooth - maybe Noise 2 Noise 4 Pacifica -Palette - maybe -Phased - maybe Plasma Ripple Running Dual -Saw - maybe -Sinelon Dual - maybe -Tetrix - maybe Twinklecat Twinkleup +MAYBE GOOD PATTERNS +Fillnoise +Gradient +Juggle +Meteor Smooth +Palette +Phased +Saw +Sinelon Dual +Tetrix + */ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 01539dc63c..3856c0e373 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -8,7 +8,7 @@ #include "global_state.h" #include "radio.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 144; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 255; const static CommandId COMMAND_UPDATE = 0x411; const static CommandId COMMAND_NEXT = 0x321; @@ -327,7 +327,9 @@ class PatternController : public MessageReceiver { } uint16_t set_next_effect(uint16_t phrase) { - EffectDef def = gEffects[random8(gEffectCount)]; + uint8_t effect_num = random8(gEffectCount); + + EffectDef def = gEffects[effect_num]; if (def.control.energy > this->energy) def = gEffects[0]; @@ -643,10 +645,8 @@ class PatternController : public MessageReceiver { return; case 'g': - /* PARTICLES for (int i=0; i< 10; i++) addGlitter(); - */ break; case '?': diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index f4ad7532c2..245510aa75 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -100,21 +100,20 @@ class Effects { } void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { - unsigned int len = 0; /* PARTICLES particles.length(); */ + unsigned int len = numParticles; for (unsigned i=len; i > 0; i--) { Particle *particle = particles[i-1]; particle->update(frame); if (particle->age > particle->lifetime) { - delete particle; - /* PARTICLES particles.erase(i-1); */ + removeParticle(i-1); continue; } } } void draw(CRGB strip[], uint8_t num_leds) { - uint8_t len = 0; /* PARTICLES particles.length(); */ + uint8_t len = numParticles; for (uint8_t i=0; idrawFn(particle, strip, num_leds); diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d49f10a1bb..d31e4185d7 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -3,9 +3,8 @@ #include "wled.h" #include "beats.h" #include "options.h" -// #include "ustd.h" -#define MAX_PARTICLES 20 +#define MAX_PARTICLES 80 #undef PARTICLE_PALETTES class Particle; @@ -154,25 +153,35 @@ class Particle { }; -Particle* particles[5]; -/* PARTICLES -ustd::array particles = ustd::array(5); -*/ +Particle* particles[MAX_PARTICLES]; BeatFrame_24_8 particle_beat_frame; +uint8_t numParticles = 0; + +void removeParticle(uint8_t i) { + if (i >= numParticles) + return; + + // Free the memory of the old particle + Particle *old_particle = particles[i]; + delete old_particle; + + // Reset the current free particle + int rest = numParticles - i; + if (rest > 0) { + memmove(&particles[i], &particles[i+1], sizeof(particles[0]) * rest); + } + + numParticles -= 1; +} void addParticle(Particle *particle) { particle->born = particle_beat_frame; - /* PARTICLES - particles.add(particle); - if (particles.length() > MAX_PARTICLES) { - Particle *old_particle = particles[0]; - delete old_particle; - particles.erase(0); + if (numParticles >= MAX_PARTICLES) { + removeParticle(0); } - */ + particles[numParticles++] = particle; } - void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 86e3883c0c..53cb3df15a 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -2,11 +2,11 @@ #define RADIO_H #include -#include #define RADIO_VERSION 1 #ifdef USERADIO +#include NRFLite _radio(Serial); #endif diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index dfbf76e9d2..2312724709 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -35,7 +35,8 @@ BeatFrame_24_8 swing(BeatFrame_24_8 frame) { } class VirtualStrip { - const static uint16_t DEF_BRIGHT = 192; + // Let WLED do the dimming + const static uint16_t DEF_BRIGHT = 255; public: CRGB leds[MAX_VIRTUAL_LEDS]; From 006a24910aea7232c93be4d6603b866d70dd009e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 13 Jul 2022 01:05:37 -0700 Subject: [PATCH 005/263] lower default brightness a bit to emphasize effects --- usermods/Tubes/controller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3856c0e373..f873863a3a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -8,7 +8,7 @@ #include "global_state.h" #include "radio.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 255; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static CommandId COMMAND_UPDATE = 0x411; const static CommandId COMMAND_NEXT = 0x321; From 6146ecc87b700907790f1284602f16e60a630ed1 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 18 Jul 2022 00:35:22 -0700 Subject: [PATCH 006/263] Introduce BLE --- usermods/Tubes/controller.h | 25 +++++----- usermods/Tubes/radio.h | 98 +++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 64 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index f873863a3a..fcba001e19 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -10,13 +10,14 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -const static CommandId COMMAND_UPDATE = 0x411; -const static CommandId COMMAND_NEXT = 0x321; -const static CommandId COMMAND_RESET = 0x911; -const static CommandId COMMAND_FIREWORK = 0xFFF; -const static CommandId COMMAND_HELLO = 0x000; -const static CommandId COMMAND_OPTIONS = 0x123; -const static CommandId COMMAND_BRIGHTNESS = 0x888; +const static CommandId COMMAND_HELLO = 0x00; +const static CommandId COMMAND_OPTIONS = 0x10; +const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_NEXT = 0x30; +const static CommandId COMMAND_RESET = 0xF0; + +const static CommandId COMMAND_BRIGHTNESS = 0x80; +const static CommandId COMMAND_FIREWORK = 0x90; typedef struct { @@ -139,9 +140,9 @@ class PatternController : public MessageReceiver { this->read_keys(); // If master has expired, clear masterId - if (this->radio->masterTubeId && this->slaveTimer.ended()) { + if (this->radio->uplinkTubeId && this->slaveTimer.ended()) { Serial.println(F("I have no master")); - this->radio->masterTubeId = 0; + this->radio->uplinkTubeId = 0; } // Update patterns to the beat @@ -162,7 +163,7 @@ class PatternController : public MessageReceiver { } // If alone or master, send out updates - if (!this->radio->masterTubeId and this->updateTimer.ended()) { + if (!this->radio->uplinkTubeId and this->updateTimer.ended()) { this->send_update(); } @@ -462,7 +463,7 @@ class PatternController : public MessageReceiver { case COMMAND_NEXT: { Serial.print(F(" next ")); - if (fromId < this->radio->masterTubeId) { + if (fromId < this->radio->uplinkTubeId) { Serial.print(F(" (ignoring)")); return; } @@ -475,7 +476,7 @@ class PatternController : public MessageReceiver { case COMMAND_UPDATE: { Serial.print(F(" update ")); - if (fromId < this->radio->masterTubeId) { + if (fromId < this->radio->uplinkTubeId) { Serial.print(F(" (ignoring)")); return; } diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 53cb3df15a..d6ae60bec9 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -1,31 +1,25 @@ -#ifndef RADIO_H -#define RADIO_H +#pragma once #include +#include #define RADIO_VERSION 1 +// #define USEBLE -#ifdef USERADIO -#include -NRFLite _radio(Serial); -#endif +#ifdef USEBLE +#include "bluetooth.h" +#include -const static uint8_t RADIO_TX_ID = 0; // Radio ID for both trans -const static uint8_t RADIO_RX_ID = 0; // Radio ID for both recv +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -const static uint8_t PIN_RADIO_CE = 9; // hardware pins -const static uint8_t PIN_RADIO_CSN = 10; // hardware pins -const static uint8_t PIN_RADIO_MOSI = 11; // hardware pins -const static uint8_t PIN_RADIO_MISO = 12; // hardware pins -const static uint8_t PIN_RADIO_SCK = 13; // hardware pins +static NimBLEUUID dataUuid(SERVICE_UUID); +#endif -#define RADIO_BITRATE NRFLite::BITRATE1MBPS // { BITRATE2MBPS, BITRATE1MBPS, BITRATE250KBPS } -#define RADIO_CHANNEL 100 + RADIO_VERSION // Channel hop with each version #define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec class Radio; -typedef uint16_t CommandId; +typedef uint8_t CommandId; typedef uint8_t TubeId; #define MESSAGE_DATA_MAX_SIZE 25 @@ -87,7 +81,8 @@ class Radio { bool alive = false; // true if radio booted up bool reported_no_radio = false; TubeId tubeId = 0; - TubeId masterTubeId = 0; + TubeId uplinkTubeId = 0; + char tube_name[20]; unsigned long radioFailures = 0; unsigned long radioRestarts = 0; @@ -97,23 +92,16 @@ class Radio { this->resetId(254); else this->resetId(); - -#ifdef USERADIO -#ifdef IS_TEENSY - SPI.setSCK(PIN_RADIO_SCK); - SPI.setMOSI(PIN_RADIO_MOSI); - SPI.setMISO(PIN_RADIO_MISO); + +#ifdef USEBLE + ble_setup(); #endif - SPI.begin(); - - this->reported_no_radio = false; - if (_radio.init(RADIO_RX_ID, PIN_RADIO_CE, PIN_RADIO_CSN, RADIO_BITRATE, RADIO_CHANNEL)) { - this->alive = true; - } - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); + Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit -#endif + } + + void update() { } void resetId(uint8_t id=0) { @@ -123,8 +111,18 @@ class Radio { Serial.print(F("My ID is ")); Serial.println(this->tubeId); - if (this->tubeId > this->masterTubeId) - this->masterTubeId = 0; + if (this->tubeId > this->uplinkTubeId) + this->uplinkTubeId = 0; + +#ifdef USEBLE + if (this->alive) + NimBLEDevice::deinit(false); + + sprintf(tube_name, "Tube %02X", this->tubeId); + NimBLEDevice::init(std::string(tube_name)); + delay(1000); + this->alive = true; +#endif } bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) @@ -134,24 +132,19 @@ class Radio { bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) { -#ifndef USERADIO - return true; -#endif - - bool sent = 0; + bool sent = false; if (!this->alive) - return sent; + return true; -#ifdef USERADIO RadioMessage message; if (size > sizeof(message.data)) { Serial.println(F("Too big to send")); - return 0; + return false; } message.tubeId = id; message.relayId = relayId; - message.command = command + (RADIO_VERSION << 12); + message.command = command + RADIO_VERSION; memset(message.data, 0, sizeof(message.data)); memcpy(message.data, data, size); uint16_t crc = calculate_crc(message.data, sizeof(message.data)); @@ -161,11 +154,14 @@ class Radio { Serial.print(message.tubeId); Serial.print(F(": ")); Serial.print(message.command, HEX); - - sent = _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); - Serial.print(sent ? F(" ok] ") : F(" failed] ")); + +#ifdef USEBLE + sent = ble_broadcast((byte *)&message, sizeof(message)); +#else + sent = true; #endif + Serial.print(sent ? F(" ok] ") : F(" failed] ")); return sent; } @@ -191,7 +187,7 @@ class Radio { return; // Ignore relayed messages if we already have a master - if (message.relayId && message.relayId <= this->masterTubeId) + if (message.relayId && message.relayId <= this->uplinkTubeId) return; // Filter out corrupt messages @@ -221,11 +217,11 @@ class Radio { return; } - if (message.tubeId != 255 && message.tubeId > this->masterTubeId) { + if (message.tubeId != 255 && message.tubeId > this->uplinkTubeId) { // Found a new master! - this->masterTubeId = message.tubeId; - Serial.print(F("All hail new master ")); - Serial.println(this->masterTubeId); + this->uplinkTubeId = message.tubeId; + Serial.print(F("My new uplink is ")); + Serial.println(this->uplinkTubeId); } // Process the command @@ -248,5 +244,3 @@ class Radio { } }; - -#endif From abdcb8266ccde979dca628f93913b60eb61a998e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 18 Jul 2022 00:35:46 -0700 Subject: [PATCH 007/263] Introduce BLE --- platformio.ini | 509 ++++++++++++++++--------------------- usermods/Tubes/Tubes.h | 1 + usermods/Tubes/bluetooth.h | 217 ++++++++++++++++ usermods/Tubes/debug.h | 8 +- 4 files changed, 441 insertions(+), 294 deletions(-) create mode 100644 usermods/Tubes/bluetooth.h diff --git a/platformio.ini b/platformio.ini index dbbe06d88c..dc5135996d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,258 +1,132 @@ ; PlatformIO Project Configuration File -; Please visit documentation: https://docs.platformio.org/page/projectconf.html +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html [platformio] -# ------------------------------------------------------------------------------ -# ENVIRONMENTS -# -# Please uncomment one of the lines below to select your board(s) -# ------------------------------------------------------------------------------ - -# Travis CI binaries (use `platformio_override.ini` when building for your own board; see `platformio_override.ini.sample` for an example) -; default_envs = travis_esp8266, travis_esp32 - -# Release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3 - -# Build everything -; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips - -# Single binaries (uncomment your board) -; default_envs = elekstube_ips -; default_envs = nodemcuv2 -; default_envs = esp8266_2m -; default_envs = esp01_1m_full -; default_envs = esp07 -; default_envs = d1_mini -; default_envs = heltec_wifi_kit_8 -; default_envs = h803wf -; default_envs = d1_mini_debug -; default_envs = d1_mini_ota -; default_envs = esp32dev -; default_envs = esp8285_4CH_MagicHome -; default_envs = esp8285_H801 -; default_envs = d1_mini_5CH_Shojo_PCB -; default_envs = wemos_shield_esp32 -; default_envs = m5atom -; default_envs = esp32_eth -; default_envs = esp32_eth_ota1mapp -; default_envs = esp32s2_saola - -src_dir = ./wled00 +default_envs = esp32_quinled_uno +src_dir = ./wled00 data_dir = ./wled00/data build_cache_dir = ~/.buildcache -extra_configs = - platformio_override.ini +extra_configs = + platformio_override.ini [common] -# ------------------------------------------------------------------------------ -# PLATFORM: -# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266 -# -# arduino core 2.6.3 = platformIO 2.3.2 -# arduino core 2.7.0 = platformIO 2.5.0 -# ------------------------------------------------------------------------------ arduino_core_2_6_3 = espressif8266@2.3.3 arduino_core_2_7_4 = espressif8266@2.6.2 arduino_core_3_0_0 = espressif8266@3.0.0 arduino_core_3_0_2 = espressif8266@3.2.0 - -# Development platforms arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage - -# Platform to use for ESP8266 platform_wled_default = ${common.arduino_core_2_7_4} -# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 - platformio/toolchain-xtensa @ ~2.40802.200502 - platformio/tool-esptool @ ~1.413.0 - platformio/tool-esptoolpy @ ~1.30000.0 - -# ------------------------------------------------------------------------------ -# FLAGS: DEBUG -# -# ------------------------------------------------------------------------------ + platformio/toolchain-xtensa @ ~2.40802.200502 + platformio/tool-esptool @ ~1.413.0 + platformio/tool-esptoolpy @ ~1.30000.0 debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM -#if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h" -#-DDEBUG_ESP_CORE is not working right now - -# ------------------------------------------------------------------------------ -# FLAGS: ldscript (available ldscripts at https://github.com/esp8266/Arduino/tree/master/tools/sdk/ld) -# ldscript_2m1m (2048 KB) = 1019 KB sketch, 4 KB eeprom, 1004 KB spiffs, 16 KB reserved -# ldscript_4m1m (4096 KB) = 1019 KB sketch, 4 KB eeprom, 1002 KB spiffs, 16 KB reserved, 2048 KB empty/ota? -# -# Available lwIP variants (macros): -# -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH = v1.4 Higher Bandwidth (default) -# -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY = v2 Lower Memory -# -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH = v2 Higher Bandwidth -# -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH -# -# BearSSL performance: -# When building with -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL, please add `board_build.f_cpu = 160000000` to the environment configuration -# -# BearSSL ciphers: -# When building on core >= 2.5, you can add the build flag -DBEARSSL_SSL_BASIC in order to build BearSSL with a limited set of ciphers: -# TLS_RSA_WITH_AES_128_CBC_SHA256 / AES128-SHA256 -# TLS_RSA_WITH_AES_256_CBC_SHA256 / AES256-SHA256 -# TLS_RSA_WITH_AES_128_CBC_SHA / AES128-SHA -# TLS_RSA_WITH_AES_256_CBC_SHA / AES256-SHA -# This reduces the OTA size with ~45KB, so it's especially useful on low memory boards (512k/1m). -# ------------------------------------------------------------------------------ -build_flags = - -DMQTT_MAX_PACKET_SIZE=1024 - -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL - -DBEARSSL_SSL_BASIC - -D CORE_DEBUG_LEVEL=0 - -D NDEBUG - #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here) - -D _IR_ENABLE_DEFAULT_=false - -D DECODE_HASH=true - -D DECODE_NEC=true - -D DECODE_SONY=true - -D DECODE_SAMSUNG=true - -D DECODE_LG=true - -DWLED_USE_MY_CONFIG - ; -D USERMOD_SENSORSTOMQTT - -build_unflags = - -# enables all features for travis CI -build_flags_all_features = - -D WLED_ENABLE_ADALIGHT - -D WLED_ENABLE_DMX - -D WLED_ENABLE_MQTT - -D WLED_ENABLE_WEBSOCKETS - +build_flags = + -DMQTT_MAX_PACKET_SIZE=1024 + -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL + -DBEARSSL_SSL_BASIC + -D CORE_DEBUG_LEVEL=0 + -D NDEBUG + -D _IR_ENABLE_DEFAULT_=false + -D DECODE_HASH=true + -D DECODE_NEC=true + -D DECODE_SONY=true + -D DECODE_SAMSUNG=true + -D DECODE_LG=true + -DWLED_USE_MY_CONFIG +build_unflags = +build_flags_all_features = + -D WLED_ENABLE_ADALIGHT + -D WLED_ENABLE_DMX + -D WLED_ENABLE_MQTT + -D WLED_ENABLE_WEBSOCKETS build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags} -build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} - +build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} ldscript_1m128k = eagle.flash.1m128.ld ldscript_2m512k = eagle.flash.2m512.ld ldscript_2m1m = eagle.flash.2m1m.ld ldscript_4m1m = eagle.flash.4m1m.ld [scripts_defaults] -extra_scripts = - pre:pio-scripts/set_version.py - post:pio-scripts/output_bins.py - post:pio-scripts/strip-floats.py - pre:pio-scripts/user_config_copy.py - -# ------------------------------------------------------------------------------ -# COMMON SETTINGS: -# ------------------------------------------------------------------------------ +extra_scripts = + pre:pio-scripts/set_version.py + post:pio-scripts/output_bins.py + post:pio-scripts/strip-floats.py + pre:pio-scripts/user_config_copy.py + [env] framework = arduino board_build.flash_mode = dout monitor_speed = 115200 -# slow upload speed (comment this out with a ';' when building for development use) upload_speed = 115200 -# fast upload speed (remove ';' when building for development use) -; upload_speed = 921600 - -# ------------------------------------------------------------------------------ -# LIBRARIES: required dependencies -# Please note that we don't always use the latest version of a library. -# -# The following libraries have been included (and some of them changd) in the source: -# ArduinoJson@5.13.5, Blynk@0.5.4(changed), E131@1.0.0(changed), Time@1.5, Timezone@1.2.1 -# ------------------------------------------------------------------------------ lib_compat_mode = strict -lib_deps = - fastled/FastLED @ 3.5.0 - IRremoteESP8266 @ 2.8.2 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4 - #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line - #TFT_eSPI - #For use SSD1306 OLED display uncomment following - #U8g2@~2.27.2 - #For Dallas sensor uncomment following 2 lines - #OneWire@~2.3.5 - #milesburton/DallasTemperature@^3.9.0 - #For BME280 sensor uncomment following - #BME280@~3.0.0 - ; adafruit/Adafruit BMP280 Library @ 2.1.0 - ; adafruit/Adafruit CCS811 Library @ 1.0.4 - ; adafruit/Adafruit Si7021 Library @ 1.4.0 - +lib_deps = + fastled/FastLED @ 3.5.0 + IRremoteESP8266 @ 2.8.2 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4 extra_scripts = ${scripts_defaults.extra_scripts} [esp8266] -build_flags = - -DESP8266 - -DFP_IN_IROM - ;-Wno-deprecated-declarations - -Wno-register +build_flags = + -DESP8266 + -DFP_IN_IROM + -Wno-register -Wno-misleading-indentation -; NONOSDK22x_190703 = 2.2.2-dev(38a443e) - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 -; lwIP 2 - Higher Bandwidth no Features -; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH -; lwIP 1.4 - Higher Bandwidth (Aircoookie has) - -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH -; VTABLES in Flash - -DVTABLES_IN_FLASH -; restrict to minimal mime-types - -DMIMETYPE_MINIMAL - -lib_deps = - ${env.lib_deps} - #https://github.com/lorol/LITTLEFS.git - ESPAsyncTCP @ 1.2.2 - ESPAsyncUDP - makuna/NeoPixelBus @ 2.6.9 + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + -DVTABLES_IN_FLASH + -DMIMETYPE_MINIMAL +lib_deps = + ${env.lib_deps} + ESPAsyncTCP @ 1.2.2 + ESPAsyncUDP + makuna/NeoPixelBus @ 2.6.9 [esp32] -#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip platform = espressif32@3.5.0 - platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 - build_flags = -g - -DARDUINO_ARCH_ESP32 - #-DCONFIG_LITTLEFS_FOR_IDF_3_2 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x - -D LOROL_LITTLEFS - + -DARDUINO_ARCH_ESP32 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -D LOROL_LITTLEFS default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv - -lib_deps = - ${env.lib_deps} - https://github.com/lorol/LITTLEFS.git - makuna/NeoPixelBus @ 2.6.9 - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 +lib_deps = + ${env.lib_deps} + https://github.com/lorol/LITTLEFS.git + makuna/NeoPixelBus @ 2.6.9 + https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 [esp32s2] build_flags = -g - -DARDUINO_ARCH_ESP32 - -DARDUINO_ARCH_ESP32S2 - -DCONFIG_IDF_TARGET_ESP32S2 - -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DCO - -lib_deps = - ${env.lib_deps} - makuna/NeoPixelBus @ 2.6.9 - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + -DARDUINO_ARCH_ESP32 + -DARDUINO_ARCH_ESP32S2 + -DCONFIG_IDF_TARGET_ESP32S2 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -DCO +lib_deps = + ${env.lib_deps} + makuna/NeoPixelBus @ 2.6.9 + https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 [esp32c3] build_flags = -g - -DARDUINO_ARCH_ESP32 - -DARDUINO_ARCH_ESP32C3 - -DCONFIG_IDF_TARGET_ESP32C3 - -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DCO - -lib_deps = - ${env.lib_deps} - makuna/NeoPixelBus @ 2.6.9 - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 - -# ------------------------------------------------------------------------------ -# WLED BUILDS -# ------------------------------------------------------------------------------ + -DARDUINO_ARCH_ESP32 + -DARDUINO_ARCH_ESP32C3 + -DCONFIG_IDF_TARGET_ESP32C3 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -DCO +lib_deps = + ${env.lib_deps} + makuna/NeoPixelBus @ 2.6.9 + https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 [env:nodemcuv2] board = nodemcuv2 @@ -261,7 +135,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:esp8266_2m] board = esp_wroom_02 @@ -270,7 +146,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:esp01_1m_full] board = esp01_1m @@ -279,7 +157,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:esp07] board = esp07 @@ -288,7 +168,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:d1_mini] board = d1_mini @@ -298,7 +180,9 @@ upload_speed = 921600 board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 monitor_filters = esp8266_exception_decoder [env:heltec_wifi_kit_8] @@ -308,7 +192,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:h803wf] board = d1_mini @@ -317,15 +203,19 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D LEDPIN=1 -D WLED_DISABLE_INFRARED -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:esp32dev] board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET -lib_deps = ${esp32.lib_deps} +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 +lib_deps = + ${esp32.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} @@ -336,29 +226,35 @@ platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_BLYNK -lib_deps = ${esp32.lib_deps} +lib_deps = + ${esp32.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 board_build.partitions = ${esp32.default_partitions} [env:esp32s2_saola] board = esp32-s2-saola-1 platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip -platform_packages = +platform_packages = framework = arduino board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv board_build.flash_mode = qio upload_speed = 460800 build_unflags = ${common.build_unflags} -lib_deps = ${esp32s2.lib_deps} +lib_deps = + ${esp32s2.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:esp32c3] board = esp32-c3-devkitm-1 platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip -platform_packages = +platform_packages = framework = arduino board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv upload_speed = 460800 build_unflags = ${common.build_unflags} -lib_deps = ${esp32c3.lib_deps} +lib_deps = + ${esp32c3.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:esp8285_4CH_MagicHome] board = esp8285 @@ -367,7 +263,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:esp8285_H801] board = esp8285 @@ -376,7 +274,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:d1_mini_5CH_Shojo_PCB] board = d1_mini @@ -385,11 +285,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_USE_SHOJO_PCB -lib_deps = ${esp8266.lib_deps} - -# ------------------------------------------------------------------------------ -# DEVELOPMENT BOARDS -# ------------------------------------------------------------------------------ +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:d1_mini_debug] board = d1_mini @@ -399,19 +297,22 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} ${common.debug_flags} -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:d1_mini_ota] board = d1_mini upload_protocol = espota -# exchange for your WLED IP upload_port = "10.10.1.27" platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:anavi_miracle_controller] board = d1_mini @@ -420,11 +321,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 -lib_deps = ${esp8266.lib_deps} - -# ------------------------------------------------------------------------------ -# custom board configurations -# ------------------------------------------------------------------------------ +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:wemos_shield_esp32] board = esp32dev @@ -432,24 +331,28 @@ platform = espressif32@3.2 upload_speed = 460800 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} - -D LEDPIN=16 - -D RLYPIN=19 - -D BTNPIN=17 - -D IRPIN=18 - -D UWLED_USE_MY_CONFIG - -D USERMOD_DALLASTEMPERATURE - -D USERMOD_FOUR_LINE_DISPLAY - -D TEMPERATURE_PIN=23 -lib_deps = ${esp32.lib_deps} - OneWire@~2.3.5 - olikraus/U8g2 @ ^2.28.8 + -D LEDPIN=16 + -D RLYPIN=19 + -D BTNPIN=17 + -D IRPIN=18 + -D UWLED_USE_MY_CONFIG + -D USERMOD_DALLASTEMPERATURE + -D USERMOD_FOUR_LINE_DISPLAY + -D TEMPERATURE_PIN=23 +lib_deps = + ${esp32.lib_deps} + OneWire@~2.3.5 + olikraus/U8g2 @ ^2.28.8 + h2zero/NimBLE-Arduino@^1.4.0 board_build.partitions = ${esp32.default_partitions} [env:m5atom] board = esp32dev build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 -lib_deps = ${esp32.lib_deps} +lib_deps = + ${esp32.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 platform = espressif32@3.2 board_build.partitions = ${esp32.default_partitions} @@ -458,28 +361,36 @@ board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1 -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:sp511e] board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:athom7w] board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:athom15w] board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} build_flags = ${common.build_flags_esp8266} -D WLED_USE_IC_CCT -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:MY9291] board = esp01_1m @@ -488,28 +399,22 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291 -lib_deps = ${esp8266.lib_deps} - -# ------------------------------------------------------------------------------ -# travis test board configurations -# ------------------------------------------------------------------------------ +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:travis_esp8266] extends = env:d1_mini build_type = debug build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} ${common.debug_flags} ${common.build_flags_all_features} +lib_deps = h2zero/NimBLE-Arduino@^1.4.0 [env:travis_esp32] extends = env:esp32dev -; build_type = debug build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_flags_all_features} - -# ------------------------------------------------------------------------------ -# codm pixel controller board configurations -# codm-controller-0.6 can also be used for the TYWE3S controller -# ------------------------------------------------------------------------------ +lib_deps = h2zero/NimBLE-Arduino@^1.4.0 [env:codm-controller-0.6] board = esp_wroom_02 @@ -518,7 +423,9 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 [env:codm-controller-0.6-rev2] board = esp_wroom_02 @@ -527,37 +434,53 @@ platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} +lib_deps = + ${esp8266.lib_deps} + h2zero/NimBLE-Arduino@^1.4.0 -# ------------------------------------------------------------------------------ -# EleksTube-IPS -# ------------------------------------------------------------------------------ [env:elekstube_ips] board = esp32dev platform = espressif32@3.2 upload_speed = 921600 build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED - -D USERMOD_RTC - -D USERMOD_ELEKSTUBE_IPS - -D LEDPIN=12 - -D RLYPIN=27 - -D BTNPIN=34 - -D WLED_DISABLE_BLYNK - -D DEFAULT_LED_COUNT=6 - # Display config - -D ST7789_DRIVER - -D TFT_WIDTH=135 - -D TFT_HEIGHT=240 - -D CGRAM_OFFSET - -D TFT_SDA_READ - -D TFT_MOSI=23 - -D TFT_SCLK=18 - -D TFT_DC=25 - -D TFT_RST=26 - -D SPI_FREQUENCY=40000000 - -D USER_SETUP_LOADED + -D USERMOD_RTC + -D USERMOD_ELEKSTUBE_IPS + -D LEDPIN=12 + -D RLYPIN=27 + -D BTNPIN=34 + -D WLED_DISABLE_BLYNK + -D DEFAULT_LED_COUNT=6 + -D ST7789_DRIVER + -D TFT_WIDTH=135 + -D TFT_HEIGHT=240 + -D CGRAM_OFFSET + -D TFT_SDA_READ + -D TFT_MOSI=23 + -D TFT_SCLK=18 + -D TFT_DC=25 + -D TFT_RST=26 + -D SPI_FREQUENCY=40000000 + -D USER_SETUP_LOADED monitor_filters = esp32_exception_decoder -lib_deps = - ${esp32.lib_deps} - TFT_eSPI @ ^2.3.70 +lib_deps = + ${esp32.lib_deps} + TFT_eSPI @ ^2.3.70 + h2zero/NimBLE-Arduino@^1.4.0 board_build.partitions = ${esp32.default_partitions} + +[env:esp32_heltec] +extends = env:esp32dev +board = heltec_wifi_lora_32_v2 +lib_deps = h2zero/NimBLE-Arduino@^1.4.0 + +[env:esp32_quinled_uno] +extends = env:esp32dev +platform = espressif32@3.2 +build_flags = ${common.build_flags_esp32} -D RLYPIN=15 -D BTNPIN=0 -D NUM_STRIPS=2 -D PIXEL_COUNTS="64,64" -D DATA_PINS="16,3" -D DEFAULT_LED_COUNT=128 -D WLED_USE_ETHERNET -D USERMOD_TUBES +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP +lib_deps = + ${esp32.lib_deps} + muwerk/ustd@^0.7.1 + h2zero/NimBLE-Arduino@^1.4.0 diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index ccc87488b1..d82375b659 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -55,6 +55,7 @@ class TubesUsermod : public Usermod { beats.update(); controller.update(); debug.update(); + radio.update(); // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h new file mode 100644 index 0000000000..fa2e68c5dd --- /dev/null +++ b/usermods/Tubes/bluetooth.h @@ -0,0 +1,217 @@ +#pragma once + +#include +#include + +static NimBLEServer* pServer; + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer) { + Serial.println("Client connected"); + Serial.println("Multi-connect support: start advertising"); + NimBLEDevice::startAdvertising(); + }; + + /** Alternative onConnect() method to extract details of the connection. + * See: src/ble_gap.h for the details of the ble_gap_conn_desc struct. + */ + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + Serial.print("Client address: "); + Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + /** We can use the connection handle here to ask for different connection parameters. + * Args: connection handle, min connection interval, max connection interval + * latency, supervision timeout. + * Units; Min/Max Intervals: 1.25 millisecond increments. + * Latency: number of intervals allowed to skip. + * Timeout: 10 millisecond increments, try for 5x interval time for best results. + */ + pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60); + }; + + void onDisconnect(NimBLEServer* pServer) { + Serial.println("Client disconnected - start advertising"); + NimBLEDevice::startAdvertising(); + }; + + void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) { + Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); + }; + +/********************* Security handled here ********************** +****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + Serial.println("Server Passkey Request"); + /** This should return a random 6 digit number for security + * or make your own static passkey as done here. + */ + return 123456; + }; + + bool onConfirmPIN(uint32_t pass_key){ + Serial.print("The passkey YES/NO number: ");Serial.println(pass_key); + /** Return false if passkeys don't match. */ + return true; + }; + + void onAuthenticationComplete(ble_gap_conn_desc* desc){ + /** Check that encryption was successful, if not we disconnect the client */ + if(!desc->sec_state.encrypted) { + NimBLEDevice::getServer()->disconnect(desc->conn_handle); + Serial.println("Encrypt connection failed - disconnecting client"); + return; + } + Serial.println("Starting BLE work!"); + }; +}; + +/** Handler class for characteristic actions */ +class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { + void onRead(NimBLECharacteristic* pCharacteristic){ + Serial.print(pCharacteristic->getUUID().toString().c_str()); + Serial.print(": onRead(), value: "); + Serial.println(pCharacteristic->getValue().c_str()); + }; + + void onWrite(NimBLECharacteristic* pCharacteristic) { + Serial.print(pCharacteristic->getUUID().toString().c_str()); + Serial.print(": onWrite(), value: "); + Serial.println(pCharacteristic->getValue().c_str()); + }; + + /** Called before notification or indication is sent, + * the value can be changed here before sending if desired. + */ + void onNotify(NimBLECharacteristic* pCharacteristic) { + Serial.println("Sending notification to clients"); + }; + + + /** The status returned in status is defined in NimBLECharacteristic.h. + * The value returned in code is the NimBLE host return code. + */ + void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) { + String str = ("Notification/Indication status code: "); + str += status; + str += ", return code: "; + str += code; + str += ", "; + str += NimBLEUtils::returnCodeToString(code); + Serial.println(str); + }; + + void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) { + String str = "Client ID: "; + str += desc->conn_handle; + str += " Address: "; + str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str(); + if(subValue == 0) { + str += " Unsubscribed to "; + }else if(subValue == 1) { + str += " Subscribed to notfications for "; + } else if(subValue == 2) { + str += " Subscribed to indications for "; + } else if(subValue == 3) { + str += " Subscribed to notifications and indications for "; + } + str += std::string(pCharacteristic->getUUID()).c_str(); + + Serial.println(str); + }; +}; + +/** Handler class for descriptor actions */ +class DescriptorCallbacks : public NimBLEDescriptorCallbacks { + void onWrite(NimBLEDescriptor* pDescriptor) { + std::string dscVal = pDescriptor->getValue(); + Serial.print("Descriptor witten value:"); + Serial.println(dscVal.c_str()); + }; + + void onRead(NimBLEDescriptor* pDescriptor) { + Serial.print(pDescriptor->getUUID().toString().c_str()); + Serial.println(" Descriptor read"); + }; +}; + + +/** Define callback instances globally to use for multiple Charateristics \ Descriptors */ +static DescriptorCallbacks dscCallbacks; +static CharacteristicCallbacks chrCallbacks; + + +void ble_setup() { + esp_coex_preference_set(ESP_COEX_PREFER_BT); + return; + +// /** Optional: set the transmit power, default is 3db */ +// #ifdef ESP_PLATFORM +// NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ +// #else +// NimBLEDevice::setPower(9); /** +9db */ +// #endif + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); + NimBLEDevice::setSecurityAuth(false, false, true); + + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks()); + + NimBLEService* pTubeService = pServer->createService("D00B"); + NimBLECharacteristic* pFoodCharacteristic = pTubeService->createCharacteristic( + "FEED", + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + ); + + pFoodCharacteristic->setValue("Fries"); + pFoodCharacteristic->setCallbacks(&chrCallbacks); + + /** Start the services when finished creating all Characteristics and Descriptors */ + pTubeService->start(); + + /** Add the services to the advertisment data **/ + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(pTubeService->getUUID()); + pAdvertising->setScanResponse(false); + pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); + // pAdvertising->start(); + + Serial.println("Advertising Started"); + delay(1000); +} + + +bool ble_broadcast(byte *data, int size) { + return true; + + // Broadcast the current effect state to every connected client + if (pServer->getConnectedCount() == 0) + return true; + + NimBLEService* pTubeService = pServer->getServiceByUUID("D00B"); + if (!pTubeService) + return true; + + NimBLECharacteristic* pFoodCharacteristic = pTubeService->getCharacteristic("FEED"); + if(!pFoodCharacteristic) + return true; + + // Update the characteristic + byte buffer[40]; + if (size > 40) { + Serial.println("Too much data"); + return false; + } + memcpy(buffer, data, size); + + pFoodCharacteristic->setValue(buffer); + pFoodCharacteristic->notify(true); + + return true; +} \ No newline at end of file diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 8f4717ab89..6134fab0db 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -27,6 +27,12 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { + Serial.printf("IP: %u.%u.%u.%u ", + WiFi.localIP()[0], + WiFi.localIP()[1], + WiFi.localIP()[2], + WiFi.localIP()[3] + ); Serial.print(F("Free memory: ")); Serial.println( freeMemory() ); } @@ -40,7 +46,7 @@ class DebugController { uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->masterTubeId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->radio->uplinkTubeId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { From a22105a77739f67197b8dad59044ddaf06e4cc91 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 21 Jul 2022 00:55:29 -0700 Subject: [PATCH 008/263] More BLE investigation --- usermods/Tubes/bluetooth.h | 34 ++++++++++++++++++++++++++-------- usermods/Tubes/radio.h | 22 +--------------------- wled00/wled.h | 1 + 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index fa2e68c5dd..21c8e66e14 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -3,8 +3,11 @@ #include #include +#define USEBLE static NimBLEServer* pServer; +bool initialized = false; + /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ class ServerCallbacks: public NimBLEServerCallbacks { @@ -141,16 +144,29 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; +void ble_init(char *tube_name) { +#ifndef USEBLE + return; +#endif + if (initialized) + NimBLEDevice::deinit(false); + + NimBLEDevice::init(std::string(tube_name)); + initialized = true; +} + void ble_setup() { - esp_coex_preference_set(ESP_COEX_PREFER_BT); +#ifndef USEBLE return; +#endif + esp_coex_preference_set(ESP_COEX_PREFER_BT); -// /** Optional: set the transmit power, default is 3db */ -// #ifdef ESP_PLATFORM -// NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ -// #else -// NimBLEDevice::setPower(9); /** +9db */ -// #endif + /** Optional: set the transmit power, default is 3db */ +#ifdef ESP_PLATFORM + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ +#else + NimBLEDevice::setPower(9); /** +9db */ +#endif /** Set the IO capabilities of the device, each option will trigger a different pairing method. * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing @@ -188,8 +204,10 @@ void ble_setup() { bool ble_broadcast(byte *data, int size) { +#ifndef USEBLE return true; - +#endif + // Broadcast the current effect state to every connected client if (pServer->getConnectedCount() == 0) return true; diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index d6ae60bec9..aa826ded72 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -4,16 +4,8 @@ #include #define RADIO_VERSION 1 -// #define USEBLE -#ifdef USEBLE #include "bluetooth.h" -#include - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" - -static NimBLEUUID dataUuid(SERVICE_UUID); -#endif #define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec @@ -93,9 +85,7 @@ class Radio { else this->resetId(); -#ifdef USEBLE ble_setup(); -#endif Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit @@ -114,15 +104,9 @@ class Radio { if (this->tubeId > this->uplinkTubeId) this->uplinkTubeId = 0; -#ifdef USEBLE - if (this->alive) - NimBLEDevice::deinit(false); - sprintf(tube_name, "Tube %02X", this->tubeId); - NimBLEDevice::init(std::string(tube_name)); - delay(1000); + ble_init(tube_name); this->alive = true; -#endif } bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) @@ -155,11 +139,7 @@ class Radio { Serial.print(F(": ")); Serial.print(message.command, HEX); -#ifdef USEBLE sent = ble_broadcast((byte *)&message, sizeof(message)); -#else - sent = true; -#endif Serial.print(sent ? F(" ok] ") : F(" failed] ")); return sent; diff --git a/wled00/wled.h b/wled00/wled.h index 7aaa2b0135..d35b21f593 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -16,6 +16,7 @@ #define WLED_DISABLE_LOXONE #define WLED_DISABLE_ALEXA #define WLED_DISABLE_INFRARED +#define WLED_DISABLE_CRONIXIE // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From f42da91b52c0ee41cd4eb4be67e052d030954e89 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 21 Jul 2022 03:02:54 -0700 Subject: [PATCH 009/263] Fix BLE initialization --- usermods/Tubes/bluetooth.h | 43 +++++++++++++++++--------------------- usermods/Tubes/radio.h | 9 +++++--- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 21c8e66e14..95d17607d3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -6,7 +6,7 @@ #define USEBLE static NimBLEServer* pServer; -bool initialized = false; +bool bluetooth_on = false; /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ @@ -144,23 +144,7 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; -void ble_init(char *tube_name) { -#ifndef USEBLE - return; -#endif - if (initialized) - NimBLEDevice::deinit(false); - - NimBLEDevice::init(std::string(tube_name)); - initialized = true; -} - void ble_setup() { -#ifndef USEBLE - return; -#endif - esp_coex_preference_set(ESP_COEX_PREFER_BT); - /** Optional: set the transmit power, default is 3db */ #ifdef ESP_PLATFORM NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ @@ -196,17 +180,14 @@ void ble_setup() { pAdvertising->addServiceUUID(pTubeService->getUUID()); pAdvertising->setScanResponse(false); pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - // pAdvertising->start(); + pAdvertising->start(); Serial.println("Advertising Started"); - delay(1000); } - bool ble_broadcast(byte *data, int size) { -#ifndef USEBLE - return true; -#endif + if (!bluetooth_on) + return true; // Broadcast the current effect state to every connected client if (pServer->getConnectedCount() == 0) @@ -232,4 +213,18 @@ bool ble_broadcast(byte *data, int size) { pFoodCharacteristic->notify(true); return true; -} \ No newline at end of file +} + +void ble_init(char *name) { + if (bluetooth_on) { + NimBLEDevice::deinit(false); + bluetooth_on = false; + } + + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + esp_coex_preference_set(ESP_COEX_PREFER_BT); + NimBLEDevice::init(std::string(name)); + ble_setup(); + bluetooth_on = true; +} + diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index aa826ded72..1dff0a82d2 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -85,13 +85,15 @@ class Radio { else this->resetId(); - ble_setup(); - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit } void update() { + if (millis() > 500 && !bluetooth_on) { + Serial.println("INITIALIZE BLUETOOTH"); + ble_init(tube_name); + } } void resetId(uint8_t id=0) { @@ -105,7 +107,8 @@ class Radio { this->uplinkTubeId = 0; sprintf(tube_name, "Tube %02X", this->tubeId); - ble_init(tube_name); + if (bluetooth_on) + ble_init(tube_name); this->alive = true; } From f7d182118a1f904c30b5425e7652496090fdd2f3 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 22 Jul 2022 05:03:36 -0700 Subject: [PATCH 010/263] New bluetooth mesh code --- usermods/Tubes/bluetooth.h | 282 ++++++++++++++++++++++++++---------- usermods/Tubes/controller.h | 40 +---- usermods/Tubes/debug.h | 4 +- usermods/Tubes/radio.h | 57 ++------ 4 files changed, 231 insertions(+), 152 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 95d17607d3..52217e1315 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -3,10 +3,16 @@ #include #include -#define USEBLE -static NimBLEServer* pServer; -bool bluetooth_on = false; +typedef uint16_t MeshId; + +typedef struct { + MeshId id = 0; + MeshId uplinkId = 0; +} MeshIds; + + +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ @@ -138,93 +144,223 @@ class DescriptorCallbacks : public NimBLEDescriptorCallbacks { }; }; - /** Define callback instances globally to use for multiple Charateristics \ Descriptors */ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; -void ble_setup() { - /** Optional: set the transmit power, default is 3db */ -#ifdef ESP_PLATFORM - NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ -#else - NimBLEDevice::setPower(9); /** +9db */ -#endif +class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { + public: + bool alive = false; // true if radio booted up - /** Set the IO capabilities of the device, each option will trigger a different pairing method. - * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing - * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing - * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing - */ - NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); - NimBLEDevice::setSecurityAuth(false, false, true); + MeshIds ids; + byte buffer[100]; + char node_name[20]; - pServer = NimBLEDevice::createServer(); - pServer->setCallbacks(new ServerCallbacks()); + uint16_t serviceUUID = 0xD00F; - NimBLEService* pTubeService = pServer->createService("D00B"); - NimBLECharacteristic* pFoodCharacteristic = pTubeService->createCharacteristic( - "FEED", - NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST - ); + NimBLEServer* pServer = NULL; + NimBLEService* pService = NULL; + NimBLEScan* pScanner = NULL; - pFoodCharacteristic->setValue("Fries"); - pFoodCharacteristic->setCallbacks(&chrCallbacks); + Timer uplinkTimer; - /** Start the services when finished creating all Characteristics and Descriptors */ - pTubeService->start(); + MeshId newMeshId() { + return random(0, 4000); + } - /** Add the services to the advertisment data **/ - NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); - pAdvertising->addServiceUUID(pTubeService->getUUID()); - pAdvertising->setScanResponse(false); - pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - pAdvertising->start(); + void advertise() { + if (!pService) + return; - Serial.println("Advertising Started"); -} + // Add the services to the advertisement data + auto pAdvertising = NimBLEDevice::getAdvertising(); + if (!pAdvertising) + return; -bool ble_broadcast(byte *data, int size) { - if (!bluetooth_on) - return true; + auto service_data = std::string((char *)&ids, sizeof(ids)); + if (ids.uplinkId) + sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); + else + sprintf(node_name, "Tube %03X", ids.id); - // Broadcast the current effect state to every connected client - if (pServer->getConnectedCount() == 0) - return true; + // // // Reset the device name + // NimBLEDevice::deinit(false); + // NimBLEDevice::init(node_name); - NimBLEService* pTubeService = pServer->getServiceByUUID("D00B"); - if (!pTubeService) - return true; + // Set advertisement + pAdvertising->stop(); + pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); + pAdvertising->start(); - NimBLECharacteristic* pFoodCharacteristic = pTubeService->getCharacteristic("FEED"); - if(!pFoodCharacteristic) - return true; - - // Update the characteristic - byte buffer[40]; - if (size > 40) { - Serial.println("Too much data"); - return false; + Serial.printf("Advertising %s", node_name); + Serial.println(); + } + + void init_service() { + /** Optional: set the transmit power, default is 3db */ + #ifdef ESP_PLATFORM + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ + #else + NimBLEDevice::setPower(9); /** +9db */ + #endif + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); + NimBLEDevice::setSecurityAuth(false, false, true); + + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks()); + + pService = pServer->createService("D00B"); + NimBLECharacteristic* pCharacteristic = pService->createCharacteristic( + "FEED", + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + ); + + pCharacteristic->setValue("Test"); + pCharacteristic->setCallbacks(&chrCallbacks); + + /** Start the services when finished creating all Characteristics and Descriptors */ + pService->start(); + + /** Add the services to the advertisement data **/ + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(pService->getUUID()); + pAdvertising->setScanResponse(false); + pAdvertising->setAppearance(0x07C6); // Multi-color LED array + pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); + + advertise(); } - memcpy(buffer, data, size); - - pFoodCharacteristic->setValue(buffer); - pFoodCharacteristic->notify(true); - - return true; -} - -void ble_init(char *name) { - if (bluetooth_on) { - NimBLEDevice::deinit(false); - bluetooth_on = false; + + void init_scanner() { + NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); + NimBLEDevice::setScanDuplicateCacheSize(200); + + pScanner = NimBLEDevice::getScan(); //create new scan + // Set the callback for when devices are discovered, no duplicates. + pScanner->setAdvertisedDeviceCallbacks(this, false); + pScanner->setActiveScan(false); // Don't request data (it uses more energy) + pScanner->setInterval(200); // How often the scan occurs / switches channels; in milliseconds, + pScanner->setWindow(80); // How long to scan during the interval; in milliseconds. + pScanner->setMaxResults(0); // do not store the scan results, use callback only. + } + + void init() { + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + esp_coex_preference_set(ESP_COEX_PREFER_BT); + NimBLEDevice::init(std::string("Tube")); + init_scanner(); + init_service(); + this->alive = true; + } + + void uplink_ping() { + // Track the last time we received a message from our master + this->uplinkTimer.start(UPLINK_TIMEOUT); } - esp_wifi_set_ps(WIFI_PS_MIN_MODEM); - esp_coex_preference_set(ESP_COEX_PREFER_BT); - NimBLEDevice::init(std::string(name)); - ble_setup(); - bluetooth_on = true; -} + void update() { + // Don't do anything for the first half-second to avoid crashing WiFi + if (millis() < 500) + return; + + if (!this->alive) { + this->init(); + } + + // Check the last time we heard from the uplink node + if (is_following() && this->uplinkTimer.ended()) { + Serial.println("Uplink lost"); + follow(0); + } + + if (!this->pScanner->isScanning()) { + // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. + this->pScanner->start(0, nullptr, false); + } + } + void broadcast(byte *data, int size) { + if (size > sizeof(buffer)) { + Serial.println("Too much data"); + return; + } + + memset(buffer, 0, sizeof(buffer)); + memcpy(buffer, data, size); + + if (!pServer) + return; + + // Broadcast the current effect state to every connected client + if (pServer->getConnectedCount() == 0) + return; + + if (!pService) + return; + + NimBLECharacteristic* pCharacteristic = pService->getCharacteristic("FEED"); + if(!pCharacteristic) + return; + + // Update the characteristic + pCharacteristic->setValue(buffer); + pCharacteristic->notify(true); + } + + void reset(MeshId id = 0) { + if (id == 0) + id = newMeshId(); + this->ids.id = id; + + Serial.printf("My ID is %03X", this->ids.id); + if (this->ids.id > this->ids.uplinkId) + this->ids.uplinkId = 0; + + advertise(); + } + + void follow(MeshId uplinkId) { + if (this->ids.uplinkId == uplinkId) + return; + + this->ids.uplinkId = uplinkId; + advertise(); + } + + bool is_following() { + return this->ids.uplinkId != 0; + } + + // ====== CALLBACKS ======= + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { + if (!advertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) + return; + + // Make sure it's booted up and advertising data + auto data = advertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); + if (!data.length()) + return; + + MeshIds data_ids; + memcpy(&data_ids, data.c_str(), data.length()); + Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); + + if (data_ids.id >= ids.uplinkId) { + follow(data_ids.id); + uplink_ping(); + } + + Serial.printf("Found: %s: %s\n", + std::string(advertisedDevice->getAddress()).c_str(), + std::string(advertisedDevice->getName()).c_str() + ); + } + +}; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index fcba001e19..8e0c2c435b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -74,7 +74,6 @@ class PatternController : public MessageReceiver { Timer graphicsTimer; Timer updateTimer; - Timer slaveTimer; #ifdef USELCD Lcd *lcd; @@ -131,7 +130,6 @@ class PatternController : public MessageReceiver { this->radio->setup(this->isMaster); this->radio->sendCommand(COMMAND_HELLO); - this->slaveTimer.start(RADIO_SENDPERIOD * 3); // Assume we're a slave at first, just listen for a master. this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to } @@ -139,12 +137,6 @@ class PatternController : public MessageReceiver { { this->read_keys(); - // If master has expired, clear masterId - if (this->radio->uplinkTubeId && this->slaveTimer.ended()) { - Serial.println(F("I have no master")); - this->radio->uplinkTubeId = 0; - } - // Update patterns to the beat this->update_beat(); @@ -162,8 +154,8 @@ class PatternController : public MessageReceiver { this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); } - // If alone or master, send out updates - if (!this->radio->uplinkTubeId and this->updateTimer.ended()) { + // Update current status + if (this->updateTimer.ended()) { this->send_update(); } @@ -220,19 +212,8 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - if (this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state))) { - this->radio->radioFailures = 0; - this->updateTimer.snooze(RADIO_SENDPERIOD); - } else { - // might have been a collision. Back off by a small amount determined by ID - Serial.println(F("Radio update failed")); - this->updateTimer.snooze( this->radio->tubeId & 0x7F ); - this->radio->radioFailures++; - if (this->radio->radioFailures > 100) { - this->radio->setup(this->isMaster); - this->radio->radioRestarts++; - } - } + this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state)); + this->updateTimer.snooze(RADIO_SENDPERIOD); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -463,10 +444,6 @@ class PatternController : public MessageReceiver { case COMMAND_NEXT: { Serial.print(F(" next ")); - if (fromId < this->radio->uplinkTubeId) { - Serial.print(F(" (ignoring)")); - return; - } memcpy(&this->next_state, data, sizeof(TubeState)); this->next_state.print(); @@ -476,19 +453,12 @@ class PatternController : public MessageReceiver { case COMMAND_UPDATE: { Serial.print(F(" update ")); - if (fromId < this->radio->uplinkTubeId) { - Serial.print(F(" (ignoring)")); - return; - } TubeState state; memcpy(&state, data, sizeof(TubeState)); state.print(); Serial.print(F(" (obeying)")); - // Track the last time we received a message from our master - this->slaveTimer.start(RADIO_SENDPERIOD * 8); - // Catch up to this state this->load_pattern(state); this->load_palette(state); @@ -559,7 +529,7 @@ class PatternController : public MessageReceiver { break; case 'i': - this->radio->resetId(arg >> 8); + this->radio->mesh_node.reset(arg >> 8); break; case 'd': diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 6134fab0db..861ae393bd 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -43,10 +43,10 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->radio->mesh_node.ids.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->uplinkTubeId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->radio->mesh_node.ids.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 1dff0a82d2..3f44c41754 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -12,17 +12,15 @@ class Radio; typedef uint8_t CommandId; -typedef uint8_t TubeId; #define MESSAGE_DATA_MAX_SIZE 25 typedef struct { CommandId command; - TubeId tubeId; - TubeId relayId; byte data[MESSAGE_DATA_MAX_SIZE]; uint16_t crc = 0; } RadioMessage; + class MessageReceiver { public: @@ -50,9 +48,6 @@ uint16_t calculate_crc( byte *data, byte len ) { return crc & 65535; } -uint8_t newTubeId() { - return random(10, 250); // Leave room for master -} void printMessageData(RadioMessage &message, int size) { Serial.print(sizeof(message.data)); @@ -72,52 +67,32 @@ class Radio { public: bool alive = false; // true if radio booted up bool reported_no_radio = false; - TubeId tubeId = 0; - TubeId uplinkTubeId = 0; - char tube_name[20]; + + BLEMeshNode mesh_node = BLEMeshNode(); unsigned long radioFailures = 0; unsigned long radioRestarts = 0; void setup(bool isMaster) { if (isMaster) - this->resetId(254); + mesh_node.reset(4500); else - this->resetId(); + mesh_node.reset(); Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit } void update() { - if (millis() > 500 && !bluetooth_on) { - Serial.println("INITIALIZE BLUETOOTH"); - ble_init(tube_name); - } - } - - void resetId(uint8_t id=0) { - if (id == 0) - id = newTubeId(); - this->tubeId = id; - Serial.print(F("My ID is ")); - Serial.println(this->tubeId); - - if (this->tubeId > this->uplinkTubeId) - this->uplinkTubeId = 0; - - sprintf(tube_name, "Tube %02X", this->tubeId); - if (bluetooth_on) - ble_init(tube_name); - this->alive = true; + mesh_node.update(); } - bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) { - return this->sendCommandFrom(this->tubeId, command, data, size, relayId); + return this->sendCommandFrom(0, command, data, size, relayId); } - bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + bool sendCommandFrom(MeshId id, uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) { bool sent = false; if (!this->alive) @@ -129,8 +104,6 @@ class Radio { return false; } - message.tubeId = id; - message.relayId = relayId; message.command = command + RADIO_VERSION; memset(message.data, 0, sizeof(message.data)); memcpy(message.data, data, size); @@ -138,14 +111,14 @@ class Radio { message.crc = crc; Serial.print(F("[")); - Serial.print(message.tubeId); + Serial.print(mesh_node.ids.id); Serial.print(F(": ")); Serial.print(message.command, HEX); - sent = ble_broadcast((byte *)&message, sizeof(message)); + mesh_node.broadcast((byte *)&message, sizeof(message)); Serial.print(sent ? F(" ok] ") : F(" failed] ")); - return sent; + return true; } void receiveCommands(MessageReceiver *receiver) @@ -170,7 +143,7 @@ class Radio { return; // Ignore relayed messages if we already have a master - if (message.relayId && message.relayId <= this->uplinkTubeId) + if (message.uplinkId && message.uplinkId <= this->uplinkTubeId) return; // Filter out corrupt messages @@ -193,7 +166,7 @@ class Radio { // Ignore messages from a lower ID if (message.tubeId < this->tubeId) { // Don't need to be noisy about relayed messages - if (message.relayId == 0) { + if (message.uplinkId == 0) { Serial.print(F("Ignoring message from ")); Serial.println(message.tubeId); } @@ -216,7 +189,7 @@ class Radio { Serial.print(F(" (relaying as ")); Serial.print(this->tubeId); Serial.print(F(")")); - message.relayId = message.tubeId; + message.reluplinkIdayId = message.tubeId; message.tubeId = this->tubeId; _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); } From 2bb3f3e22964de4b045a69c5b895c828e84b1dd4 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 22 Jul 2022 05:19:24 -0700 Subject: [PATCH 011/263] Fix uplinkId following --- usermods/Tubes/bluetooth.h | 39 +++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 52217e1315..1784bc57a3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -152,6 +152,7 @@ static CharacteristicCallbacks chrCallbacks; class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { public: bool alive = false; // true if radio booted up + bool changed = false; MeshIds ids; byte buffer[100]; @@ -179,10 +180,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { return; auto service_data = std::string((char *)&ids, sizeof(ids)); - if (ids.uplinkId) - sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); - else - sprintf(node_name, "Tube %03X", ids.id); + sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); // // // Reset the device name // NimBLEDevice::deinit(false); @@ -235,7 +233,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->setAppearance(0x07C6); // Multi-color LED array pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - advertise(); + changed = true; } void init_scanner() { @@ -280,6 +278,11 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { follow(0); } + if (changed) { + advertise(); + changed = false; + } + if (!this->pScanner->isScanning()) { // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. this->pScanner->start(0, nullptr, false); @@ -317,25 +320,31 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void reset(MeshId id = 0) { if (id == 0) id = newMeshId(); - this->ids.id = id; - - Serial.printf("My ID is %03X", this->ids.id); - if (this->ids.id > this->ids.uplinkId) - this->ids.uplinkId = 0; + Serial.printf("My new ID is %03X", id); - advertise(); + this->ids.id = id; + MeshId uplinkId = this->ids.uplinkId; + if (id > uplinkId) + uplinkId = id; + follow(uplinkId); + changed = true; } void follow(MeshId uplinkId) { + if (uplinkId == 0) { + // Following zero means "follow yourself" + uplinkId = this->ids.id; + } + if (this->ids.uplinkId == uplinkId) return; this->ids.uplinkId = uplinkId; - advertise(); + changed = true; } bool is_following() { - return this->ids.uplinkId != 0; + return this->ids.uplinkId != 0 && this->ids.uplinkId != this->ids.id; } // ====== CALLBACKS ======= @@ -352,8 +361,8 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { memcpy(&data_ids, data.c_str(), data.length()); Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); - if (data_ids.id >= ids.uplinkId) { - follow(data_ids.id); + if (data_ids.uplinkId >= ids.uplinkId) { + follow(data_ids.uplinkId); uplink_ping(); } From 97f995f23aca7e6c54f1cc186af7421cb8c712e5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 23 Jul 2022 00:00:03 -0700 Subject: [PATCH 012/263] Update status via mesh network --- usermods/Tubes/Tubes.h | 5 +- usermods/Tubes/bluetooth.h | 316 ++++++++++++++++++++++++++-------- usermods/Tubes/controller.h | 70 ++------ usermods/Tubes/debug.h | 25 +-- usermods/Tubes/global_state.h | 9 + usermods/Tubes/radio.h | 202 ---------------------- 6 files changed, 276 insertions(+), 351 deletions(-) delete mode 100644 usermods/Tubes/radio.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index d82375b659..303666c9bb 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -18,15 +18,13 @@ #include "led_strip.h" #include "controller.h" -#include "radio.h" #include "debug.h" class TubesUsermod : public Usermod { private: BeatController beats; - Radio radio; - PatternController controller = PatternController(MAX_REAL_LEDS, &beats, &radio); + PatternController controller = PatternController(MAX_REAL_LEDS, &beats); DebugController debug = DebugController(&controller); int* master = NULL; /* master.h deleted */ @@ -55,7 +53,6 @@ class TubesUsermod : public Usermod { beats.update(); controller.update(); debug.update(); - radio.update(); // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 1784bc57a3..e2a40d9355 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -2,25 +2,54 @@ #include #include +#include "global_state.h" +#define UPDATE_RATE 2000 // Rate at which uplink is queried for data +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +#define CURRENT_MESH_VERSION 1 +#define MAX_CONNECTED_CLIENTS 3 + +#define DATA_UPDATE_SERVICE "D00B" typedef uint16_t MeshId; typedef struct { - MeshId id = 0; - MeshId uplinkId = 0; -} MeshIds; + MeshId id = 0; + MeshId uplinkId = 0; + uint8_t version = CURRENT_MESH_VERSION; +} MeshNodeHeader; +typedef struct { + MeshNodeHeader header; + TubeState current; + TubeState next; +} MeshStorage; -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +// Asynchronous queue handling +typedef struct { + MeshId id; + NimBLEAddress address; +} MeshUpdateRequest; + +class MessageReceiver { + public: + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + +static TaskHandle_t xUpdaterTaskHandle; +QueueHandle_t UpdaterQueue = xQueueCreate(5, sizeof(MeshUpdateRequest)); +void procUpdaterTask(void* pvParameters); /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ class ServerCallbacks: public NimBLEServerCallbacks { void onConnect(NimBLEServer* pServer) { Serial.println("Client connected"); - Serial.println("Multi-connect support: start advertising"); - NimBLEDevice::startAdvertising(); + if (pServer->getConnectedCount() < MAX_CONNECTED_CLIENTS) + NimBLEDevice::startAdvertising(); }; /** Alternative onConnect() method to extract details of the connection. @@ -39,11 +68,6 @@ class ServerCallbacks: public NimBLEServerCallbacks { pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60); }; - void onDisconnect(NimBLEServer* pServer) { - Serial.println("Client disconnected - start advertising"); - NimBLEDevice::startAdvertising(); - }; - void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) { Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); }; @@ -87,6 +111,8 @@ class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { Serial.print(pCharacteristic->getUUID().toString().c_str()); Serial.print(": onWrite(), value: "); Serial.println(pCharacteristic->getValue().c_str()); + + ESP.restart(); }; /** Called before notification or indication is sent, @@ -149,25 +175,71 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; +NimBLEClient* connectToServer(NimBLEAddress &peer_address) { + NimBLEClient* pClient = nullptr; + bool known_server = false; + + // Check if we have a client we should reuse + if (NimBLEDevice::getClientListSize()) { + // If we already know this peer, send false as the second argument in connect() + // to prevent refreshing the service database. This saves considerable time and power. + pClient = NimBLEDevice::getClientByPeerAddress(peer_address); + if (pClient) + known_server = true; + else + pClient = NimBLEDevice::getDisconnectedClient(); + + if (pClient) { + if (pClient->connect(peer_address, known_server)) + return pClient; + + // Failed to connect. Just drop the client rather than deleting it. + return NULL; + } + } + + // No client to reuse; create a new one - if we can. + if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { + Serial.println("Max clients reached - no more connections available"); + return NULL; + } + pClient = NimBLEDevice::createClient(); + + // Set up this client and attempt to connect. + pClient->setConnectionParams(12,12,0,51); + pClient->setConnectTimeout(5); + if (!pClient->connect(peer_address)) { + // Created a client but failed to connect, don't need to keep it as it has no data. + NimBLEDevice::deleteClient(pClient); + return NULL; + } + + return pClient; +} + + class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { public: bool alive = false; // true if radio booted up bool changed = false; - MeshIds ids; - byte buffer[100]; + MeshNodeHeader ids; char node_name[20]; uint16_t serviceUUID = 0xD00F; - NimBLEServer* pServer = NULL; - NimBLEService* pService = NULL; - NimBLEScan* pScanner = NULL; + NimBLEServer* pServer = nullptr; + NimBLEService* pService = nullptr; + NimBLEScan* pScanner = nullptr; + NimBLEAddress uplink_address; + + MessageReceiver *receiver = nullptr; Timer uplinkTimer; + Timer updateTimer; - MeshId newMeshId() { - return random(0, 4000); + BLEMeshNode(MessageReceiver *receiver) { + this->receiver = receiver; } void advertise() { @@ -182,7 +254,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { auto service_data = std::string((char *)&ids, sizeof(ids)); sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); - // // // Reset the device name + // Reset the device name: // NimBLEDevice::deinit(false); // NimBLEDevice::init(node_name); @@ -191,8 +263,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); pAdvertising->start(); - Serial.printf("Advertising %s", node_name); - Serial.println(); + Serial.printf("Advertising %s\n", node_name); } void init_service() { @@ -213,14 +284,20 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pServer = NimBLEDevice::createServer(); pServer->setCallbacks(new ServerCallbacks()); + pServer->advertiseOnDisconnect(true); - pService = pServer->createService("D00B"); + pService = pServer->createService(DATA_UPDATE_SERVICE); NimBLECharacteristic* pCharacteristic = pService->createCharacteristic( "FEED", - NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + NIMBLE_PROPERTY::READ ); + pCharacteristic->setValue(""); + pCharacteristic->setCallbacks(&chrCallbacks); - pCharacteristic->setValue("Test"); + pCharacteristic = pService->createCharacteristic( + "F00D", + NIMBLE_PROPERTY::WRITE + ); pCharacteristic->setCallbacks(&chrCallbacks); /** Start the services when finished creating all Characteristics and Descriptors */ @@ -238,34 +315,67 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void init_scanner() { NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); - NimBLEDevice::setScanDuplicateCacheSize(200); + NimBLEDevice::setScanDuplicateCacheSize(20); pScanner = NimBLEDevice::getScan(); //create new scan // Set the callback for when devices are discovered, no duplicates. pScanner->setAdvertisedDeviceCallbacks(this, false); pScanner->setActiveScan(false); // Don't request data (it uses more energy) - pScanner->setInterval(200); // How often the scan occurs / switches channels; in milliseconds, - pScanner->setWindow(80); // How long to scan during the interval; in milliseconds. + pScanner->setInterval(97); // How often the scan occurs / switches channels; in milliseconds, + pScanner->setWindow(37); // How long to scan during the interval; in milliseconds. pScanner->setMaxResults(0); // do not store the scan results, use callback only. } + void init_updater() { + xTaskCreate( + procUpdaterTask, /* Function to implement the task */ + "UpdaterTask", /* Name of the task */ + 3840, /* Stack size in bytes */ + this, /* Task input parameter */ + tskIDLE_PRIORITY+1, /* Priority of the task */ + &xUpdaterTaskHandle /* Task handle. */ + ); + } + void init() { esp_wifi_set_ps(WIFI_PS_MIN_MODEM); esp_coex_preference_set(ESP_COEX_PREFER_BT); NimBLEDevice::init(std::string("Tube")); init_scanner(); init_service(); + init_updater(); this->alive = true; } - void uplink_ping() { - // Track the last time we received a message from our master - this->uplinkTimer.start(UPLINK_TIMEOUT); + void onPeerPing(MeshNodeHeader* pRemoteNode, NimBLEAdvertisedDevice* pAdvertisedDevice) { + Serial.printf("Found %03X/%03X at %s\n", + pRemoteNode->id, + pRemoteNode->uplinkId, + std::string(pAdvertisedDevice->getAddress()).c_str() + ); + + if (pRemoteNode->id == ids.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + if (pRemoteNode->id > ids.id && pRemoteNode->id > ids.uplinkId) { + follow(pRemoteNode->id, pAdvertisedDevice); + } + + if (pRemoteNode->id == ids.uplinkId) { + this->onUplinkAlive(); + } + } + + void setup() { + this->reset(); + Serial.println("Mesh: ok"); } void update() { - // Don't do anything for the first half-second to avoid crashing WiFi - if (millis() < 500) + // Don't do anything for the first second to avoid crashing WiFi + if (millis() < 1000) return; if (!this->alive) { @@ -278,6 +388,18 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { follow(0); } + if (this->ids.uplinkId && this->updateTimer.ended()) { + MeshUpdateRequest request = { + .id = this->ids.uplinkId, + .address = this->uplink_address + }; + if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { + Serial.println("Update queue is full!"); + } + updateTimer.snooze(UPDATE_RATE); + } + + // If any actions caused the service to change, re-advertise with new values if (changed) { advertise(); changed = false; @@ -289,20 +411,10 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } } - void broadcast(byte *data, int size) { - if (size > sizeof(buffer)) { - Serial.println("Too much data"); - return; - } - - memset(buffer, 0, sizeof(buffer)); - memcpy(buffer, data, size); - - if (!pServer) - return; - + void update_node_storage(TubeState ¤t, TubeState &next) { // Broadcast the current effect state to every connected client - if (pServer->getConnectedCount() == 0) + + if (!pServer || pServer->getConnectedCount() == 0) return; if (!pService) @@ -312,64 +424,116 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if(!pCharacteristic) return; - // Update the characteristic - pCharacteristic->setValue(buffer); - pCharacteristic->notify(true); + // Store this data in the characteristic + MeshStorage storage = { + .header = this->ids, + .current = current, + .next = next + }; + pCharacteristic->setValue(storage); } void reset(MeshId id = 0) { if (id == 0) - id = newMeshId(); - Serial.printf("My new ID is %03X", id); - + id = random(256, 4000); // Leave room at bottom and top of 12 bits this->ids.id = id; - MeshId uplinkId = this->ids.uplinkId; - if (id > uplinkId) - uplinkId = id; - follow(uplinkId); + follow(0); changed = true; } - void follow(MeshId uplinkId) { - if (uplinkId == 0) { - // Following zero means "follow yourself" - uplinkId = this->ids.id; - } + void follow(MeshId uplinkId, NimBLEAdvertisedDevice* pAdvertisedDevice = NULL) { + // Following zero means you have no uplink + // Update uplink device address + if (uplinkId && pAdvertisedDevice) + this->uplink_address = pAdvertisedDevice->getAddress(); + else + this->uplink_address = NimBLEAddress(); + + // Update uplink ID if (this->ids.uplinkId == uplinkId) return; - this->ids.uplinkId = uplinkId; changed = true; } bool is_following() { - return this->ids.uplinkId != 0 && this->ids.uplinkId != this->ids.id; + return this->ids.uplinkId != 0; } // ====== CALLBACKS ======= - void onResult(NimBLEAdvertisedDevice* advertisedDevice) { - if (!advertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) + void onResult(NimBLEAdvertisedDevice* pAdvertisedDevice) { + // Discovered a peer via scanning. + + if (!pAdvertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) return; - // Make sure it's booted up and advertising data - auto data = advertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); - if (!data.length()) + // Make sure it's booted up and advertising Mesh IDs + auto data = pAdvertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); + if (data.length() != sizeof(MeshNodeHeader)) + return; + MeshNodeHeader* pRemoteNode = (MeshNodeHeader *)data.c_str(); + if (pRemoteNode->version != this->ids.version) return; - MeshIds data_ids; - memcpy(&data_ids, data.c_str(), data.length()); - Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); + this->onPeerPing(pRemoteNode, pAdvertisedDevice); + } - if (data_ids.uplinkId >= ids.uplinkId) { - follow(data_ids.uplinkId); - uplink_ping(); + void onUpdateData(MeshUpdateRequest &request, MeshStorage &storage) { + if (request.id != storage.header.id) { + Serial.println("Uplink is invalid!"); + // The remote server has changed its ID. We need to adapt. + follow(0); + changed = true; + return; } + this->onUplinkAlive(); - Serial.printf("Found: %s: %s\n", - std::string(advertisedDevice->getAddress()).c_str(), - std::string(advertisedDevice->getName()).c_str() + // Process the command + this->receiver->onCommand( + storage.header.id, + COMMAND_UPDATE, + &storage.current + ); + this->receiver->onCommand( + storage.header.id, + COMMAND_NEXT, + &storage.next ); } + void onUplinkAlive() { + // Track the last time we received a message from our uplink + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + }; + + +// UPDATER +// This is an async task handler that awaits requests to update data, +// then connects to the requested server and fetches the data. +void procUpdaterTask(void* pvParameters) { + BLEMeshNode *pNode = (BLEMeshNode*)pvParameters; + MeshUpdateRequest request; + + for (;;) { + // Wait to be told to update (the queue blocks) + xQueueReceive(UpdaterQueue, &request, portMAX_DELAY); + + // Got a request to update, so try to connect and pull down data. + auto uplink_address = request.address; + auto pClient = connectToServer(uplink_address); + if (!pClient) + continue; + + auto pService = pClient->getService(DATA_UPDATE_SERVICE); + if (pService) { + auto pCharacteristic = pService->getCharacteristic("FEED"); + MeshStorage storage = pCharacteristic->readValue(); + pNode->onUpdateData(request, storage); + } + pClient->disconnect(); + } + +} \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8e0c2c435b..0ae346509a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -6,18 +6,11 @@ #include "palettes.h" #include "effects.h" #include "global_state.h" -#include "radio.h" +#include "bluetooth.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; +#define STATUS_UPDATE_PERIOD 1000 -const static CommandId COMMAND_HELLO = 0x00; -const static CommandId COMMAND_OPTIONS = 0x10; -const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_NEXT = 0x30; -const static CommandId COMMAND_RESET = 0xF0; - -const static CommandId COMMAND_BRIGHTNESS = 0x80; -const static CommandId COMMAND_FIREWORK = 0x90; typedef struct { @@ -80,8 +73,8 @@ class PatternController : public MessageReceiver { #endif LEDs *led_strip; BeatController *beats; - Radio *radio; Effects *effects; + BLEMeshNode *mesh; ControllerOptions options; char key_buffer[20] = {0}; @@ -90,15 +83,15 @@ class PatternController : public MessageReceiver { TubeState current_state; TubeState next_state; - PatternController(uint8_t num_leds, BeatController *beats, Radio *radio) { + PatternController(uint8_t num_leds, BeatController *beats) { this->num_leds = num_leds; #ifdef USELCD this->lcd = new Lcd(); #endif this->led_strip = new LEDs(num_leds); this->beats = beats; - this->radio = radio; this->effects = new Effects(); + this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED @@ -112,6 +105,7 @@ class PatternController : public MessageReceiver { void setup(bool isMaster) { + this->mesh->setup(); this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; @@ -125,16 +119,15 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; - Serial.println(F("Patterns: ok")); - - this->radio->setup(this->isMaster); - this->radio->sendCommand(COMMAND_HELLO); - this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to + this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to + Serial.println("Patterns: ok"); } void update() { + this->mesh->update(); + this->read_keys(); // Update patterns to the beat @@ -157,10 +150,9 @@ class PatternController : public MessageReceiver { // Update current status if (this->updateTimer.ended()) { this->send_update(); + this->updateTimer.snooze(STATUS_UPDATE_PERIOD); } - this->radio->receiveCommands(this); - if (this->graphicsTimer.every(REFRESH_PERIOD)) { this->updateGraphics(); } @@ -212,8 +204,7 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state)); - this->updateTimer.snooze(RADIO_SENDPERIOD); + this->mesh->update_node_storage(this->current_state, this->next_state); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -225,7 +216,6 @@ class PatternController : public MessageReceiver { Serial.print(F("E: ")); this->next_state.print(); Serial.print(F(" ")); - this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); Serial.println(); } @@ -341,9 +331,11 @@ class PatternController : public MessageReceiver { } void optionsChanged() { +#ifdef NOT_COMPLETE if (this->isMaster) { this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } +#endif } void setBrightness(uint8_t brightness) { @@ -408,19 +400,12 @@ class PatternController : public MessageReceiver { addFlash(); } - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + virtual void onCommand(MeshId fromId, CommandId command, void *data) { if (fromId) { - Serial.print(F("From ")); - Serial.print(fromId); - Serial.print(F(": ")); + Serial.printf("From %03X: ", fromId); } switch (command) { - case COMMAND_FIREWORK: - Serial.print(F("fireworks")); - this->acknowledge(); - return; - case COMMAND_RESET: Serial.print(F("reset")); return; @@ -431,11 +416,6 @@ class PatternController : public MessageReceiver { return; } - case COMMAND_HELLO: - Serial.print(F("hello")); - this->updateTimer.stop(); - return; - case COMMAND_OPTIONS: { Serial.print(F("options")); memcpy(&this->options, data, sizeof(this->options)); @@ -522,16 +502,6 @@ class PatternController : public MessageReceiver { accum88 arg = this->parse_number(command+1); switch (command[0]) { - case 'f': - this->radio->sendCommandFrom(255, COMMAND_FIREWORK, NULL, 0); - this->onCommand(0, COMMAND_FIREWORK, NULL); - Serial.println(); - break; - - case 'i': - this->radio->mesh_node.reset(arg >> 8); - break; - case 'd': this->setDebugging(!this->options.debugging); break; @@ -609,12 +579,6 @@ class PatternController : public MessageReceiver { this->update_next(); return; - case 'h': - // Pretend to receive a HELLO - this->onCommand(0, COMMAND_HELLO, NULL); - Serial.println(); - return; - case 'g': for (int i=0; i< 10; i++) addGlitter(); @@ -645,7 +609,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + this->mesh->update_node_storage(this->current_state, this->next_state); } }; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 861ae393bd..a6e22109a4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,21 +1,20 @@ #pragma once #include "controller.h" -#include "radio.h" - +#include "bluetooth.h" class DebugController { public: PatternController *controller; LEDs *strip; - Radio *radio; + BLEMeshNode *mesh; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; this->strip = controller->led_strip; - this->radio = controller->radio; + this->mesh = controller->mesh; } void setup() @@ -27,14 +26,14 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("IP: %u.%u.%u.%u ", + Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d\n", + this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], - WiFi.localIP()[3] + WiFi.localIP()[3], + freeMemory() ); - Serial.print(F("Free memory: ")); - Serial.println( freeMemory() ); } // Show the beat on the master OR if debugging @@ -43,22 +42,16 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->radio->mesh_node.ids.id, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->mesh->ids.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->mesh_node.ids.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->mesh->ids.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { this->strip->leds[p3] = CRGB::Yellow; } - if (this->radio->radioRestarts) { - this->strip->leds[1] = CRGB::Red; - } } - if (this->radio->radioFailures && !this->radio->radioRestarts) { - this->strip->leds[0] = CRGB::Red; - } } }; diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index a23a769aae..d75b940f83 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -52,3 +52,12 @@ class TubeState { } }; + +typedef uint8_t CommandId; + +const static CommandId COMMAND_OPTIONS = 0x10; +const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_NEXT = 0x30; +const static CommandId COMMAND_RESET = 0xF0; + +const static CommandId COMMAND_BRIGHTNESS = 0x80; diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h deleted file mode 100644 index 3f44c41754..0000000000 --- a/usermods/Tubes/radio.h +++ /dev/null @@ -1,202 +0,0 @@ -#pragma once - -#include -#include - -#define RADIO_VERSION 1 - -#include "bluetooth.h" - -#define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec - -class Radio; - -typedef uint8_t CommandId; - -#define MESSAGE_DATA_MAX_SIZE 25 -typedef struct { - CommandId command; - byte data[MESSAGE_DATA_MAX_SIZE]; - uint16_t crc = 0; -} RadioMessage; - - -class MessageReceiver { - public: - - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } -}; - -uint16_t calculate_crc( byte *data, byte len ) { - - const unsigned long crc_table[16] = { - 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, - 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, - 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c - }; - - unsigned long crc = ~0L; - - for ( unsigned int index = 0 ; index < len ; ++index ) { - crc = crc_table[( crc ^ data[index] ) & 0x0f] ^ (crc >> 4); - crc = crc_table[( crc ^ ( data[index] >> 4 )) & 0x0f] ^ (crc >> 4); - crc = ~crc; - } - return crc & 65535; -} - - -void printMessageData(RadioMessage &message, int size) { - Serial.print(sizeof(message.data)); - Serial.print(F(":")); - for (unsigned int i = 0; i < sizeof(message.data); i++) { - if (message.data[i] < 16) - Serial.print(F("0")); - Serial.print(message.data[i], HEX); - Serial.print(F(" ")); - } - Serial.print(F("[")); - Serial.print(size); - Serial.print(F("] ")); -} - -class Radio { - public: - bool alive = false; // true if radio booted up - bool reported_no_radio = false; - - BLEMeshNode mesh_node = BLEMeshNode(); - - unsigned long radioFailures = 0; - unsigned long radioRestarts = 0; - - void setup(bool isMaster) { - if (isMaster) - mesh_node.reset(4500); - else - mesh_node.reset(); - - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); - // Start the radio, but mute & listen for a bit - } - - void update() { - mesh_node.update(); - } - - bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) - { - return this->sendCommandFrom(0, command, data, size, relayId); - } - - bool sendCommandFrom(MeshId id, uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) - { - bool sent = false; - if (!this->alive) - return true; - - RadioMessage message; - if (size > sizeof(message.data)) { - Serial.println(F("Too big to send")); - return false; - } - - message.command = command + RADIO_VERSION; - memset(message.data, 0, sizeof(message.data)); - memcpy(message.data, data, size); - uint16_t crc = calculate_crc(message.data, sizeof(message.data)); - message.crc = crc; - - Serial.print(F("[")); - Serial.print(mesh_node.ids.id); - Serial.print(F(": ")); - Serial.print(message.command, HEX); - - mesh_node.broadcast((byte *)&message, sizeof(message)); - - Serial.print(sent ? F(" ok] ") : F(" failed] ")); - return true; - } - - void receiveCommands(MessageReceiver *receiver) - { -#ifdef USERADIO - RadioMessage message; - - if (!this->alive && !this->reported_no_radio) - { - Serial.println(F("No radio")); - this->reported_no_radio = true; - return; - } - - // check for incoming data - while (_radio.hasData()) - { - _radio.readData(&message); - - // Messages must be from a tube with the current version - if ((message.command>>12) != RADIO_VERSION) - return; - - // Ignore relayed messages if we already have a master - if (message.uplinkId && message.uplinkId <= this->uplinkTubeId) - return; - - // Filter out corrupt messages - unsigned long crc = calculate_crc(message.data, sizeof(message.data)); - if (crc != message.crc) { - // Corrupt packet... ignore it. - Serial.print(F("Invalid CRC: ")); - Serial.print(message.crc); - Serial.print(F(" should be ")); - Serial.println(crc); - continue; - } - - // If we detect an ID collision, fix it by choosing a new random one - while (message.tubeId == this->tubeId) { - Serial.print(F("ID collision!")); - this->resetId(); - } - - // Ignore messages from a lower ID - if (message.tubeId < this->tubeId) { - // Don't need to be noisy about relayed messages - if (message.uplinkId == 0) { - Serial.print(F("Ignoring message from ")); - Serial.println(message.tubeId); - } - return; - } - - if (message.tubeId != 255 && message.tubeId > this->uplinkTubeId) { - // Found a new master! - this->uplinkTubeId = message.tubeId; - Serial.print(F("My new uplink is ")); - Serial.println(this->uplinkTubeId); - } - - // Process the command - receiver->onCommand(message.tubeId, message.command & 0xFFF, message.data); - - // Occcasionally relay commands - more frequently if higher ID - uint8_t r = random8(); - if ((r % 3 == 0) && r < this->tubeId) { - Serial.print(F(" (relaying as ")); - Serial.print(this->tubeId); - Serial.print(F(")")); - message.reluplinkIdayId = message.tubeId; - message.tubeId = this->tubeId; - _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); - } - - Serial.println(); - } -#endif - } - -}; From 2f4c6d52493689268cfde38e19616b876ae9673d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 23 Jul 2022 09:09:14 -0700 Subject: [PATCH 013/263] Fix controller upates; add better debugging --- usermods/Tubes/bluetooth.h | 6 ++++-- usermods/Tubes/controller.h | 9 +++++---- usermods/Tubes/debug.h | 24 ++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index e2a40d9355..4cc0f69e05 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -34,7 +34,7 @@ typedef struct { class MessageReceiver { public: - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + virtual void onCommand(MeshId fromId, CommandId command, void *data) { // Abstract: subclasses must define } }; @@ -370,6 +370,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void setup() { this->reset(); + this->updateTimer.start(UPDATE_RATE); Serial.println("Mesh: ok"); } @@ -389,6 +390,8 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } if (this->ids.uplinkId && this->updateTimer.ended()) { + this->updateTimer.snooze(UPDATE_RATE); + MeshUpdateRequest request = { .id = this->ids.uplinkId, .address = this->uplink_address @@ -396,7 +399,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { Serial.println("Update queue is full!"); } - updateTimer.snooze(UPDATE_RATE); } // If any actions caused the service to change, re-advertise with new values diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 0ae346509a..ec9e4083af 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -407,17 +407,18 @@ class PatternController : public MessageReceiver { switch (command) { case COMMAND_RESET: - Serial.print(F("reset")); + Serial.println(F("reset")); return; case COMMAND_BRIGHTNESS: { uint8_t *bright = (uint8_t *)data; this->setBrightness(*bright); + Serial.println(); return; } case COMMAND_OPTIONS: { - Serial.print(F("options")); + Serial.println(F("options")); memcpy(&this->options, data, sizeof(this->options)); return; } @@ -427,7 +428,7 @@ class PatternController : public MessageReceiver { memcpy(&this->next_state, data, sizeof(TubeState)); this->next_state.print(); - Serial.print(F(" (obeying)")); + Serial.println(F(" (obeying)")); return; } @@ -437,7 +438,7 @@ class PatternController : public MessageReceiver { TubeState state; memcpy(&state, data, sizeof(TubeState)); state.print(); - Serial.print(F(" (obeying)")); + Serial.println(F(" (obeying)")); // Catch up to this state this->load_pattern(state); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index a6e22109a4..445feb508f 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -3,6 +3,24 @@ #include "controller.h" #include "bluetooth.h" +std::string formatted_time(long ms) { + long secs = ms / 1000; // set the seconds remaining + long mins = secs / 60; //convert seconds to minutes + long hours = mins / 60; //convert minutes to hours + long days = hours / 24; //convert hours to days + + secs = secs % 60; + mins = mins % 60; + hours = hours % 24; + + char buffer[100]; + if (days > 0) + sprintf(buffer, "%ld %02ld:%02ld:%02ld", days, hours, mins, secs); + else + sprintf(buffer, "%02ld:%02ld:%02ld", hours, mins, secs); + return std::string(buffer); +} + class DebugController { public: PatternController *controller; @@ -26,13 +44,14 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d\n", + Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n", this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3], - freeMemory() + freeMemory(), + formatted_time(millis()).c_str() ); } @@ -55,3 +74,4 @@ class DebugController { } }; + From 81013fb171bdc353e0b5e0bc856ecb1d931c016b Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 26 Jul 2022 00:54:48 -0700 Subject: [PATCH 014/263] easier to read debug --- usermods/Tubes/controller.h | 1 + usermods/Tubes/debug.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ec9e4083af..3669c25355 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -201,6 +201,7 @@ class PatternController : public MessageReceiver { } void send_update() { + Serial.print(" "); this->current_state.print(); Serial.print(F(" ")); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 445feb508f..8d47faa0b4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -44,7 +44,7 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n", + Serial.printf("\n=== %s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], From 054b7a4ba8575421fc1abf274cd0e8f5f2d53163 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 26 Jul 2022 09:05:39 -0700 Subject: [PATCH 015/263] Add TimeSync - let's see if it works --- usermods/Tubes/TimeSync/counter.h | 409 +++++++++++++++++++++++++++++ usermods/Tubes/TimeSync/sync.h | 180 +++++++++++++ usermods/Tubes/TimeSync/timesync.h | 397 ++++++++++++++++++++++++++++ usermods/Tubes/bluetooth.h | 2 + usermods/Tubes/controller.h | 3 + usermods/Tubes/effects.h | 2 + usermods/Tubes/pattern.h | 9 + usermods/Tubes/virtual_strip.h | 2 + 8 files changed, 1004 insertions(+) create mode 100644 usermods/Tubes/TimeSync/counter.h create mode 100644 usermods/Tubes/TimeSync/sync.h create mode 100644 usermods/Tubes/TimeSync/timesync.h diff --git a/usermods/Tubes/TimeSync/counter.h b/usermods/Tubes/TimeSync/counter.h new file mode 100644 index 0000000000..a35187de1c --- /dev/null +++ b/usermods/Tubes/TimeSync/counter.h @@ -0,0 +1,409 @@ +/** \file + \brief Counter Math + \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Counter nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +/** \page Counter Math + + Represents an unsigned counter that can roll over from its maximum value + back to zero. A common example is a 32-bit timestamp from GetTickCount() + on Windows, which can roll-over and cause software bugs despite testing. + This also provides compression for counters. + + This class provides: + + Counters of 2 bits through 64 bits e.g. 24-bit counters + + Increment/decrement by 1 or a constant + + Safe comparison operator overloads + + Compression/decompression via truncation + + Unit tested software +*/ + +#include +#include + +// Compiler-specific force inline keyword +#if defined(_MSC_VER) + #define COUNTER_FORCE_INLINE inline __forceinline +#else // _MSC_VER + #define COUNTER_FORCE_INLINE inline __attribute__((always_inline)) +#endif // _MSC_VER + + +//------------------------------------------------------------------------------ +// Counter + +template class Counter +{ +public: + typedef Counter ThisType; + typedef T ValueType; + typedef typename std::make_signed::type SignedType; + + static const unsigned kBits = TkBits; ///< Number of data bits + static const T kMSB = (T)1 << (TkBits - 1); ///< Most Significant Bit + + /// Generate a bit mask with 1s for each data bit + /// The mask gets optimized away when compiler optimizations are enabled + static const T kMask = static_cast(-(int64_t)1) >> (sizeof(T) * 8 - kBits); + + /// Counter value + T Value; + + + //-------------------------------------------------------------------------- + // Assignment + + Counter(T value = 0) + : Value(value & kMask) + { + } + Counter(const ThisType& b) + : Value(b.Value) + { + } + + ThisType& operator=(T value) + { + Value = value & kMask; + return *this; + } + ThisType& operator=(const ThisType b) + { + Value = b.Value; + return *this; + } + + + //-------------------------------------------------------------------------- + // Accessors + + /// Get current value + COUNTER_FORCE_INLINE T ToUnsigned() const + { + return Value; + } + + + //-------------------------------------------------------------------------- + // Increment + + /// Pre-increment + COUNTER_FORCE_INLINE ThisType& operator++() + { + Value = (Value + 1) & kMask; + return *this; + } + + /// Pre-decrement + COUNTER_FORCE_INLINE ThisType& operator--() + { + Value = (Value - 1) & kMask; + return *this; + } + + /// Post-increment + COUNTER_FORCE_INLINE ThisType operator++(int) + { + const T oldValue = Value; + Value = (Value + 1) & kMask; + return oldValue; + } + + /// Post-decrement + COUNTER_FORCE_INLINE ThisType operator--(int) + { + const T oldValue = Value; + Value = (Value - 1) & kMask; + return oldValue; + } + + + //-------------------------------------------------------------------------- + // Addition + + COUNTER_FORCE_INLINE ThisType& operator+=(const ThisType b) + { + Value = (Value + b.Value) & kMask; + return *this; + } + + COUNTER_FORCE_INLINE ThisType operator+(const ThisType b) const + { + return Value + b.Value; // Implicit mask + } + + COUNTER_FORCE_INLINE ThisType& operator-=(const ThisType b) + { + Value = (Value - b.Value) & kMask; + return *this; + } + + COUNTER_FORCE_INLINE ThisType operator-(const ThisType b) const + { + return Value - b.Value; // Implicit mask + } + + + //-------------------------------------------------------------------------- + // Comparison + // We can only compare counters of the same type + + COUNTER_FORCE_INLINE bool operator==(const ThisType b) const + { + return Value == b.Value; + } + COUNTER_FORCE_INLINE bool operator!=(const ThisType b) const + { + return Value != b.Value; + } + COUNTER_FORCE_INLINE bool operator>=(const ThisType b) const + { + const T d = static_cast(Value - b.Value) & kMask; + return d < kMSB; + } + COUNTER_FORCE_INLINE bool operator<(const ThisType b) const + { + const T d = static_cast(Value - b.Value) & kMask; + return d >= kMSB; + } + COUNTER_FORCE_INLINE bool operator<=(const ThisType b) const + { + const T d = static_cast(b.Value - Value) & kMask; + return d < kMSB; + } + COUNTER_FORCE_INLINE bool operator>(const ThisType b) const + { + const T d = static_cast(b.Value - Value) & kMask; + return d >= kMSB; + } + + + //-------------------------------------------------------------------------- + // Counter Compression (Truncation) and Re-Expansion + + /** + These routines will truncate a counter to a smaller number of bits, + and later expand the smaller value back into the original counter value + provided with a reference counter. For example a 64-bit timestamp can + be compressed down to 24 bits, sent over a network, and then expanded + back to the original value given a local time at the receiver. + + This assumes that counters are counting up and that roll-over can only + happen one time. If a counter rolls over twice, then the resulting + expanded counter value will be incorrect. + */ + + /// Compress to smaller counter by truncating + template + COUNTER_FORCE_INLINE SmallerT Truncate() const + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + // Truncate to smaller type + return SmallerT(static_cast(Value)); + } + + /// Expand from truncated counter + /// Bias > 0 can be used to accept values farther in the past + /// Bias < 0 can be used to accept values farther in the future + template + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncatedWithBias( + const ThisType recent, + const SmallerT smaller, + const SignedType bias) + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + /** + The bits in the smaller counter were all truncated from the correct + value, so what needs to be determined now is all the higher bits. + + Examples: + + Recent Smaller => Expanded + ------ ------- -------- + 0x100 0xff 0x0ff + 0x16f 0x7f 0x17f + 0x17f 0x6f 0x16f + 0x1ff 0xa0 0x1a0 + 0x1ff 0x01 0x201 + + The choice to make is between -1, 0, +1 for the next bit position. + + Since we have no information about the high bits, it should be + sufficient to compare the recent low bits with the smaller value + in order to decide which one is correct: + + 00 - ff = -ff -> -1 + 6f - 7f = -10 -> 0 + 7f - 6f = +10 -> 0 + ff - a0 = +5f -> 0 + ff - 01 = +fe -> +1 + */ + + // First insert the low bits to get the default result + ThisType result = smaller.ToUnsigned() | (recent.ToUnsigned() & ~static_cast(SmallerT::kMask)); + + // Grab the low bits of the recent counter + const T recentLow = recent.ToUnsigned() & SmallerT::kMask; + + // If recent - smaller would be negative: + if (recentLow < smaller.ToUnsigned()) + { + // If it is large enough to roll back a MSB: + const T absDiff = smaller.ToUnsigned() - recentLow; + if (absDiff >= static_cast(SmallerT::kMSB - bias)) + result -= static_cast(SmallerT::kMSB) << 1; + } + else + { + // If it is large enough to roll ahead a MSB: + const T absDiff = recentLow - smaller.ToUnsigned(); + if (absDiff > static_cast(SmallerT::kMSB + bias)) + result += static_cast(SmallerT::kMSB) << 1; + } + + return result; + } + + /// Expand from truncated counter without any bias + template + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const SmallerT smaller) + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + ValueType smallerMSB = smaller.Value & SmallerT::kMSB; + SignedType smallerSigned = smaller.Value - (smallerMSB << 1); + + auto smallRecent = static_cast(recent.Value & SmallerT::kMask); + + // Signed gap = partial - prev + auto gap = static_cast(smallerSigned - smallRecent) & SmallerT::kMask; + + ValueType gapMSB = gap & SmallerT::kMSB; + SignedType gapSigned = gap - (gapMSB << 1); + + // Result = recent + gap + return recent.Value + gapSigned; + } + + // Template specialization to optimize cases where the word size matches + // the field size. Otherwise the extra sign handling above is not elided + // by the compiler's optimizer: + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(32 < kBits, "Smaller type must be smaller"); + + const int32_t gap = static_cast(smaller.Value - static_cast(recent.Value)); + return recent + gap; + } + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(16 < kBits, "Smaller type must be smaller"); + + const int16_t gap = static_cast( smaller.Value - static_cast(recent.Value) ); + return recent + gap; + } + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(8 < kBits, "Smaller type must be smaller"); + + const int8_t gap = static_cast(smaller.Value - static_cast(recent.Value)); + return recent + gap; + } + + + static_assert(std::is_pod::value, "Type must be a plain-old data type"); + static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(TkBits > 0, "Invalid input"); + static_assert(sizeof(T) * 8 >= TkBits, "Base type is not wide enough"); + + static_assert(kMask != 0, "bugcheck"); + static_assert(kMSB < kMask, "bugcheck"); + static_assert(kMSB != 0, "bugcheck"); +}; + + +/// Convenience declarations +typedef Counter Counter64; +typedef Counter Counter56; +typedef Counter Counter48; +typedef Counter Counter40; +typedef Counter Counter32; +typedef Counter Counter24; +typedef Counter Counter16; +typedef Counter Counter10; +typedef Counter Counter8; +typedef Counter Counter4; + +static_assert(sizeof(Counter8) == 1, "Unexpected padding"); +static_assert(sizeof(Counter16) == 2, "Unexpected padding"); +static_assert(sizeof(Counter32) == 4, "Unexpected padding"); +static_assert(sizeof(Counter64) == 8, "Unexpected padding"); + +/** + CounterExpand() + + This is a common utility function that expands a 1-7 byte truncated + counter back into a 64-bit (8 byte) counter, based on the largest + counter value seen so far. + + Preconditions: bytes > 0 && bytes < 8 +*/ +COUNTER_FORCE_INLINE Counter64 CounterExpand( + uint64_t largest, + uint64_t partial, + unsigned bytes) +{ + switch (bytes) + { + case 1: return Counter64::ExpandFromTruncated(largest, Counter8((uint8_t)partial)); + case 2: return Counter64::ExpandFromTruncated(largest, Counter16((uint16_t)partial)); + case 3: return Counter64::ExpandFromTruncated(largest, Counter24((uint32_t)partial)); + case 4: return Counter64::ExpandFromTruncated(largest, Counter32((uint32_t)partial)); + case 5: return Counter64::ExpandFromTruncated(largest, Counter40(partial)); + case 6: return Counter64::ExpandFromTruncated(largest, Counter48(partial)); + case 7: return Counter64::ExpandFromTruncated(largest, Counter56(partial)); + default: + break; + } + + return 0; +} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/sync.h b/usermods/Tubes/TimeSync/sync.h new file mode 100644 index 0000000000..da8cb7234f --- /dev/null +++ b/usermods/Tubes/TimeSync/sync.h @@ -0,0 +1,180 @@ +/** \file + \brief TimeSync: Time Synchronization + \copyright Copyright (c) 2017-2019 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of TimeSync nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "timesync.h" + + +//------------------------------------------------------------------------------ +// WindowedMinTS24 + +void WindowedMinTS24::Update( + Counter24 value, + uint64_t timestamp, + const uint64_t windowLengthTime) +{ + const Sample sample(value, timestamp); + + // On the first sample, new best sample, or if window length has expired: + if (!IsValid() || + value <= Samples[0].Value || + Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + Reset(sample); + return; + } + + // Insert the new value into the sorted array + if (value <= Samples[1].Value) + Samples[2] = Samples[1] = sample; + else if (value <= Samples[2].Value) + Samples[2] = sample; + + // Expire best if it has been the best for a long time + if (Samples[0].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + // Also expire the next best if needed + if (Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + Samples[0] = Samples[2]; + Samples[1] = sample; + } + else + { + Samples[0] = Samples[1]; + Samples[1] = Samples[2]; + } + Samples[2] = sample; + return; + } + + // Quarter of window has gone by without a better value - Use the second-best + if (Samples[1].Value == Samples[0].Value && + Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime / 4)) + { + Samples[2] = Samples[1] = sample; + return; + } + + // Half the window has gone by without a better value - Use the third-best one + if (Samples[2].Value == Samples[1].Value && + Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime / 2)) + { + Samples[2] = sample; + } +} + + +//------------------------------------------------------------------------------ +// TimeSynchronizer + +void TimeSynchronizer::OnPeerMinDeltaTS24(Counter24 minDeltaTS24) +{ + LastFC_MinDeltaTS24 = minDeltaTS24; + GotPeerUpdate = true; + + Recalculate(); +} + +unsigned TimeSynchronizer::OnAuthenticatedDatagramTimestamp( + Counter24 remoteSendTS24, + uint64_t localRecvUsec) +{ + const Counter24 localTS24 = (uint32_t)(localRecvUsec >> kTime23LostBits); + + // OWD_i + ClockDelta(L-R)_i = Local Receive Time - Remote Send Time + const Counter24 deltaTS24 = localTS24 - remoteSendTS24; + + WindowedMinTS24Deltas.Update(deltaTS24, localRecvUsec, kDriftWindowUsec); + + Recalculate(); + + // Estimated one-way-delay (OWD) for this datagram in microseconds. + // This does not include processing time only network delay and perhaps + // some delays from the Operating System when it is heavily loaded. + // Set to 0 if trip time is not available + unsigned networkTripUsec = 0; + + if (IsSynchronized()) + { + // This is equivalent to the shortest RTT/2 seen so far by any pair of packets, + // meaning that it is the average of the upstream and downstream OWD. + networkTripUsec = GetMinimumOneWayDelayUsec(); + + // While the OWD is an estimate, the relative delay between that + // smallest packet pair and the current datagram is actually precise: + const Counter24 minDeltaTS24 = GetMinDeltaTS24(); + if (deltaTS24 > minDeltaTS24) + { + const Counter24 relativeTS24 = deltaTS24 - minDeltaTS24; + networkTripUsec += relativeTS24.ToUnsigned() << kTime23LostBits; + } + + // What should happen here is if the delay of each packet varies a lot, then we should + // get pretty accurate OWD for each packet. But if the variance is low and the delays + // for upstream and downstream are asymmetric, then it will underestimate the OWD by + // half of that asymmetry. Hopefully this inaccuracy won't cause problems.. + } + + return networkTripUsec; +} + +void TimeSynchronizer::Recalculate() +{ + if (!WindowedMinTS24Deltas.IsValid() || !GotPeerUpdate) + return; + + // min(OWD_i) + ClockDelta(L-R)_i + const Counter24 minRecvDeltaTS24 = WindowedMinTS24Deltas.GetBest(); + + // min(OWD_j) + ClockDelta(R-L)_j + const Counter24 minSendDeltaTS24 = LastFC_MinDeltaTS24; + + // Assume min(OWD_i) = min(OWD_j): + // min(OWD) ~= (min(OWD_j) + min(OWD_i)) / 2 + const Counter23 minOWD_TS23 = (minSendDeltaTS24 + minRecvDeltaTS24).ToUnsigned() >> 1; + + // Assume ClockDelta(R-L)_j = -ClockDelta(L-R)_i: + // ClockDelta(R-L) ~= (ClockDelta(R-L)_j - ClockDelta(L-R)_i) / 2 + const Counter23 clockDelta_TS23 = (minSendDeltaTS24 - minRecvDeltaTS24).ToUnsigned() >> 1; + + // Calculate the time delta in microseconds + RemoteTimeDeltaUsec = clockDelta_TS23.ToUnsigned() << kTime23LostBits; + + // Calculate the minimum OWD, which may go negative and blow up.. + uint32_t min_owd_usec = minOWD_TS23.ToUnsigned() << kTime23LostBits; + + // If the implied subtraction went negative, correct to zero: + static const uint32_t kSignRolloverThreshold = (1 << 22) << kTime23LostBits; + if (min_owd_usec >= kSignRolloverThreshold) { + min_owd_usec = 0; + } + MinimumOneWayDelayUsec = min_owd_usec; + + Synchronized = true; +} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/timesync.h b/usermods/Tubes/TimeSync/timesync.h new file mode 100644 index 0000000000..48a04f5b31 --- /dev/null +++ b/usermods/Tubes/TimeSync/timesync.h @@ -0,0 +1,397 @@ +/** \file + \brief TimeSync: Time Synchronization + \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of TimeSync nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "counter.h" + +#include + +/** + Time Synchronization Protocol + + -- Motivation: + + Time synchronization is an important core component of an rUDP library, + enabling multiple advantages over reliable UDP libraries without: + + (1) This specific (new) time synchronization works better over cellular + networks than PTP/NTP. + + (2) The API provides time synchronization as a feature for applications, + enabling millisecond-accurate dead reckoning for video games, and + 16-microsecond-accurate timing for scientific applications with 2-3 bytes. + + (3) Peer2Peer NAT hole-punch can be optimized because it can use time + synchronization to initiate probes simultaneously on both peers. + + (4) Delay-based Congestion Control systems should use One Way Delay (OWD) + on each packet as a signal, which allows it to e.g. avoid causing latency + in realtime games while delivering a file transfer in the background. + All existing Delay-based CC algorithms use differential OWD rather than + proper time synchronization. By adding time synchronization, CC becomes + robust to changes in the base OWD as the end-points remain synced. + + -- Background: + + Network time synchronization can be done two ways: + (a) Broadcast - Infeasible on the Internet and so not used. + (b) Assuming that the link is symmetric, and trusting Min(RTT/2) = OWD. + Meaning that existing network time synchronization protocols like NTP and + PTP work by sending multiple probes, and then taking the probe with the + smallest round trip time to be the best data to use in the set of probes. + + Time synchronization at higher resolutions needs to be performed constantly + because clocks drift at a rate of about 1 millisecond per 10 seconds. + So, a common choice for reliable UDP game protocols is to probe for time + synchronization purposes (running NTP all the time) at a fixed interval of + e.g. 5 to 10 seconds. + + The disadvantage of all of these existing approaches is that they only use + a finite number of probes, and all probes may be slightly skewed, + especially when jitter is present, or cross-traffic, or self-congestion + from a file transfer. + + For cellular networks, existing time synchronization approaches provide + degraded results due to the 4-10 millisecond jitter on every packet. + "An End-to-End Measurement Study of Modern Cellular Data Networks" (2014) + https://www.comp.nus.edu.sg/~bleong/publications/pam14-ispcheck.pdf + + when a link is asymmetrical there is no known practical method for + performing time synchronization between two peers that meet on the Internet, + so in that case a best effort is done, and at least it will be consistent. + + -- Algorithm: + + This TimeSync protocol overcomes this jitter problem by using a massive + number of probes (every packet is a probe), greatly increasing the odds + of a minimal RTT probe. + + How it works is that every packet has a 3 byte microsecond timestamp on it, + large enough to prevent roll-over. Both sides record the receive time of + each packet as early as possible, and then throw the packet onto another + thread to process, so that the network delay is more accurate and each + client of a server can run in parallel. + + While each packet is being processed the difference in send and receive + times are compared with prior such differences. And a windowed minimum + of these differences is updated. Periodically this minimum difference + is reported to the remote peer so that both sides have both the minimum + (outgoing - incoming) difference and the minimum (incoming - outgoing) + difference. + + These minimum differences correspond to the shortest trips each way. + Effectively, it turns every single packet into a time synchronization + probe, guaranteeing that it gets the best result possible. + + -- The (Simple) Math for TimeSync: + + We measure (Smallest C2S Delta) and (Smallest S2C Delta) + through the per-packet timestamps. + + C2S = (Smallest C2S Delta) + = (Server Time @ Client Send Time) + (C2S Propagation Delay) - (Client Send Time) + = (C2S Propagation Delay) + (Clock Delta) + + S2C = (Smallest S2C Delta) + = (Client Time @ Server Send Time) + (S2C Propagation Delay) - (Server Send Time) + = (S2C Propagation Delay) - (Clock Delta) + + Such that (Propagation Delay) for each direction is minimized + independently as described previously. + + We want to solve for (Clock Delta) but there is a problem: + + Note that in the definition of C2S and S2C there are three unknowns and + only two measurements. To resolve this problem we make the assumption + that the min. propagation delays are almost the same in each direction. + + And so: + + (S2C Propagation Delay) approx equals (C2S Propagation Delay). + + Thus we can simply write: + + (Clock Delta) = (C2S - S2C) / 2 + + This gives us 23 bits of the delta between clocks, since the division + by 2 (right shift) pulls in an unknown high bit. The effect of any + link asymmetry is halved as a side effect, helping to minimize it. + + -- The (Simple) Math for Network Trip Time: + + It also provides a robust estimate of the "speed of light" between two + network hosts (minimal one-way delay). This information is then used to + calculate the network trip time for every packet that arrives: + + Let DD = Distance from the current packet timestamp difference and minimal. + DD = (Packet Receive - Packet Send) - Min(Packet Receive - Packet Send) + Packet trip time = (Minimal one-way delay) + DD. +*/ + + +//------------------------------------------------------------------------------ +// Constants + +/// Default One Way Delay (OWD) to return before time sync completes +static const uint32_t kDefaultOWDUsec = 200 * 1000; ///< 200 ms + +/// Number of bits removed from the low end of the microsecond timestamp +static const unsigned kTime23LostBits = 3; + +/// Largest 23-bit counter difference value considered positive +static const unsigned kTime23Bias = 0x200000; + +/// Error bound for 23-bit timestamps <= 8*2-1 = 15 microseconds +static const unsigned kTime23ErrorBound = (1 << kTime23LostBits) * 2 - 1; + +/// Number of bits removed from the low end of the microsecond timestamp +static const unsigned kTime16LostBits = 9; + +/// Largest 16-bit counter difference value considered positive +static const unsigned kTime16Bias = 0x4000; + +/// Error bound for 16-bit timestamps <= 512*2-1 = 1.023 milliseconds +static const unsigned kTime16ErrorBound = (1 << kTime16LostBits) * 2 - 1; + +/// Window size for WindowedMinTS24Deltas. +/// Since clocks drift over time, eventually old measurements must be ignored. +/// This is also the longest that a timing measurement will affect time synch. +/// Assumes that clocks drift 1 millisecond every 10 seconds +static const uint64_t kDriftWindowUsec = 10 * 1000 * 1000; ///< 10 seconds + + +//------------------------------------------------------------------------------ +// Types + +/// Use Counter23::Decompress to expand back to 64-bit counters +typedef Counter Counter23; + + +//------------------------------------------------------------------------------ +// WindowedMinTS24 + +/// Windowed minimum in TS24 units +class WindowedMinTS24 +{ +public: + WindowedMinTS24() {} + + struct Sample + { + /// Sample value + Counter24 Value; + + /// Timestamp of data collection + uint64_t Timestamp; + + + /// Default values and initializing constructor + explicit Sample(Counter24 value = 0, uint64_t timestamp = 0) + : Value(value) + , Timestamp(timestamp) + { + } + + /// Check if a timeout expired + inline bool TimeoutExpired(uint64_t now, uint64_t timeout) + { + return (uint64_t)(now - Timestamp) > timeout; + } + }; + + + /// Number of samples collected + static const unsigned kSampleCount = 3; + + /// Sorted samples from smallest to largest + Sample Samples[kSampleCount]; + + + /// Are there any samples? + inline bool IsValid() const + { + return Samples[0].Value != 0; ///< ish + } + + /// Get smallest sample + inline Counter24 GetBest() const + { + return Samples[0].Value; + } + + /// Reset samples + inline void Reset(const Sample sample = Sample()) + { + Samples[0] = Samples[1] = Samples[2] = sample; + } + + /// Update minimum with new value + void Update( + Counter24 value, + uint64_t timestamp, + const uint64_t windowLengthTime); +}; + + +//------------------------------------------------------------------------------ +// TimeSynchronizer + +class TimeSynchronizer +{ +public: + /** + OnPeerMinDeltaTS24() + + Call this when the peer provides its latest 24-bit MinDeltaTS24 value. + The peer should do this periodically, ideally faster in the first minute + and then settling down to once every 2 seconds or so. This can be used + as a keep-alive for example. + + minDeltaTS24: Provide the low 24 bits of the peer's delta value. + */ + void OnPeerMinDeltaTS24(Counter24 minDeltaTS24); + + /// Convert local time in microseconds to a 24-bit datagram timestamp + static inline uint32_t LocalTimeToDatagramTS24(uint64_t localUsec) + { + return (uint32_t)(localUsec >> kTime23LostBits) & 0x00ffffff; + } + + /** + OnAuthenticatedDatagramTimestamp() + + Call this when a datagram arrives with an attached 24-bit timestamp. + Ideally every UDP/IP datagram we receive will have a timestamp. + It is recommended to check if the datagram is from the peer before + accepting the timestamp on the datagram. + + remoteSendTS24: The 24-bit timestamp attached to an incoming datagram. + localRecvUsec: A recent timestamp in microsecond units. + + Returns estimated one way delay (OWD) in microseconds for this datagram. + Returns 0 if OWD is unavailable. + */ + unsigned OnAuthenticatedDatagramTimestamp( + Counter24 remoteSendTS24, + uint64_t localRecvUsec); + + + /// Get the minimum TS24 (receipt - send) delta seen in the past interval + inline Counter24 GetMinDeltaTS24() const + { + return WindowedMinTS24Deltas.GetBest(); + } + + /// Is time synchronized? + inline bool IsSynchronized() const + { + return Synchronized; + } + + /// Get the minimum one-way delay seen so far. + /// This is equivalent to the shortest RTT/2 seen so far by any pair of packets, + /// meaning that it is the average of the upstream and downstream OWD. + inline uint32_t GetMinimumOneWayDelayUsec() const + { + return MinimumOneWayDelayUsec; + } + + /// Returns 16-bit remote time field to send in a packet + inline uint16_t ToRemoteTime16(uint64_t localUsec) + { + if (!Synchronized) { + return 0; + } + + const uint16_t localTS16 = (uint16_t)(localUsec >> kTime16LostBits); + const uint16_t deltaTS16 = (uint16_t)(RemoteTimeDeltaUsec >> kTime16LostBits); + + return localTS16 + deltaTS16; + } + + /// Returns local time given local time from packet + inline uint64_t FromLocalTime16( + uint64_t localUsec, + Counter16 timestamp16) + { + return Counter64::ExpandFromTruncatedWithBias( + localUsec >> kTime16LostBits, + timestamp16, + kTime16Bias).ToUnsigned() << kTime16LostBits; + } + + /// Returns 23-bit remote time field to send in a packet + inline uint32_t ToRemoteTime23(uint64_t localUsec) + { + if (!Synchronized) { + return 0; + } + + const Counter23 localTS23 = (uint32_t)(localUsec >> kTime23LostBits); + const Counter23 deltaTS23 = RemoteTimeDeltaUsec >> kTime23LostBits; + + return (localTS23 + deltaTS23).ToUnsigned(); + } + + /// Returns local time given remote time from packet + inline uint64_t FromLocalTime23( + uint64_t localUsec, + Counter23 timestamp23) + { + return Counter64::ExpandFromTruncatedWithBias( + localUsec >> kTime23LostBits, + timestamp23, + kTime23Bias).ToUnsigned() << kTime23LostBits; + } + +protected: + /// Synchronized? + std::atomic Synchronized = ATOMIC_VAR_INIT(false); + + /// Calculated delta = (Remote time - Local time) + std::atomic RemoteTimeDeltaUsec = ATOMIC_VAR_INIT(0); ///< usec + + /// Calculated minimum OWD + std::atomic MinimumOneWayDelayUsec = ATOMIC_VAR_INIT(kDefaultOWDUsec); ///< in usec + + /// Windowed minimum value for received packet timestamp deltas + /// Keep track of the smallest (receipt - send) time delta seen so far + WindowedMinTS24 WindowedMinTS24Deltas; ///< in Timestamp24 units + + /// Keep a copy of the last MinDeltaUsec from the flow control data from peer + Counter24 LastFC_MinDeltaTS24 = 0; + + /// Is peer update received yet? + bool GotPeerUpdate = false; + + + /// Recalculate MinimumOneWayDelayUsec and RemoteTimeDeltaUsec + void Recalculate(); +}; \ No newline at end of file diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 4cc0f69e05..198e5c8fc3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -4,6 +4,8 @@ #include #include "global_state.h" +#include "TimeSync/sync.h" + #define UPDATE_RATE 2000 // Rate at which uplink is queried for data #define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost #define CURRENT_MESH_VERSION 1 diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3669c25355..d557af6524 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -356,6 +356,9 @@ class PatternController : public MessageReceiver { } SyncMode randomSyncMode() { + #ifdef TESTING_PATTERNS + return All; + #endif uint8_t r = random8(128); if (r < 40) return SinDrift; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 245510aa75..147196c334 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -131,6 +131,7 @@ typedef struct { static const EffectDef gEffects[] = { {{None}, {LongDuration}}, +#ifndef TESTING_PATTERNS {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, @@ -150,5 +151,6 @@ static const EffectDef gEffects[] = { {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, +#endif }; const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 77fdf883ed..a5b2409196 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -79,6 +79,11 @@ void biwave(VirtualStrip *strip) } } +void tick(VirtualStrip *strip) { + strip->fill(CRGB::Black); + strip->leds[strip->beat % 16] = CRGB::White; +} + void sinelon(VirtualStrip *strip) { // a colored dot sweeping back and forth, with fading trails @@ -162,6 +167,9 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. PatternDef gPatterns[] = { +#ifdef TESTING_PATTERNS + {tick, LongDuration}, +#else {drawNoise, {ShortDuration}}, {drawNoise, {ShortDuration}}, {drawNoise, {MediumDuration}}, @@ -180,6 +188,7 @@ PatternDef gPatterns[] = { {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, {bpm_palette, {MediumDuration, HighEnergy}} +#endif }; /* diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 2312724709..66d8b97baa 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -7,6 +7,8 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 +#define TESTING_PATTERNS + class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); From 39abe9c34b6107afc85f9f7140a36f31e5b7a443 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 2 Aug 2022 22:01:40 -0700 Subject: [PATCH 016/263] Make the Tube mode use the current WLED palette --- usermods/Tubes/controller.h | 19 +++++++++++++++---- usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/virtual_strip.h | 19 ++++++++++++++++--- wled00/FX.h | 15 ++++++++++++++- wled00/wled.h | 2 +- 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index d557af6524..8e4f220569 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1,5 +1,8 @@ #pragma once +#include "wled.h" +#include "FX.h" + #include "beats.h" #include "pattern.h" @@ -239,6 +242,8 @@ class PatternController : public MessageReceiver { this->background_changed(); } + // Choose the pattern to display at the next pattern cycle + // Return the number of phrases until the next pattern cycle uint16_t set_next_pattern(uint16_t phrase) { uint8_t pattern_id = random8(gPatternCount); PatternDef def = gPatterns[pattern_id]; @@ -260,23 +265,27 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) + if (this->current_state.palette_id == tube_state.palette_id) { + Serial.println("Nope, don't change"); return; + } this->current_state.palette_phrase = tube_state.palette_phrase; this->_load_palette(tube_state.palette_id); } void _load_palette(uint8_t palette_id) { - this->current_state.palette_id = palette_id % gGradientPaletteCount; + this->current_state.palette_id = palette_id; Serial.print(F("Change palette")); this->background_changed(); } + // Choose the palette to display at the next palette cycle + // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return random8(4,40); + return 1; // random8(4,40); } void load_effect(TubeState &tube_state) { @@ -299,6 +308,8 @@ class PatternController : public MessageReceiver { this->effects->load(this->current_state.effect_params); } + // Choose the effect to display at the next effect cycle + // Return the number of phrases until the next effect cycle uint16_t set_next_effect(uint16_t phrase) { uint8_t effect_num = random8(gEffectCount); @@ -320,7 +331,7 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - background.palette = gGradientPalettes[this->current_state.palette_id]; + background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; // re-use virtual strips to prevent heap fragmentation diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index ddbca2fd4b..58252d44d4 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -3,7 +3,7 @@ #define USE_WLED #include "wled.h" -#define MAX_REAL_LEDS 64 +#define MAX_REAL_LEDS 100 class LEDs { diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 66d8b97baa..e53bf82d05 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -3,11 +3,12 @@ #include "util.h" #include "options.h" #include "beats.h" +#include "palettes.h" #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -#define TESTING_PATTERNS +//#define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -15,7 +16,7 @@ typedef void (*BackgroundFn)(VirtualStrip *strip); class Background { public: BackgroundFn animate; - CRGBPalette16 palette; + uint8_t palette_id; SyncMode sync=All; }; @@ -72,6 +73,12 @@ class VirtualStrip { this->fader = 0; this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; + +#ifdef MASTER_TUBE + // Interface with WLED + WS2812FX::load_palette(background.palette_id); + stateChanged = true; +#endif } void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) @@ -157,7 +164,13 @@ class VirtualStrip { } CRGB palette_color(uint8_t c, uint8_t offset=0) { - return ColorFromPalette( this->background.palette, c + offset ); +#define WLED_COLORS +#ifdef WLED_COLORS + return WS2812FX::get_palette_crgb(c + offset); +#else + CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; + return ColorFromPalette( palette, c + offset ); +#endif } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { diff --git a/wled00/FX.h b/wled00/FX.h index 46d36d781e..98ec9bf13d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -927,9 +927,22 @@ class WS2812FX { CRGB external_buffer[EXTERNAL_BUFFER_SIZE]; // 4 bytes per element public: - static CRGB *get_external_buffer() { + static CRGB* get_external_buffer() { return instance->external_buffer; } + static CRGB get_palette_crgb(uint16_t c) { + uint32_t color = instance->color_from_palette(c, false, true, 255); + return instance->col_to_crgb(color); + } + static void load_palette(uint8_t palette_id) { + for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + WS2812FX::Segment& seg = instance->getSegment(i); + seg.palette = palette_id; + } + } + static WS2812FX* get_strip() { + return instance; + } inline bool hasWhiteChannel(void) {return _hasWhiteChannel;} inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;} diff --git a/wled00/wled.h b/wled00/wled.h index d35b21f593..deeac1260f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2203191 +#define VERSION 2208011 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From b375e7717b733abfea60c002d69fb5071e115dd1 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 2 Aug 2022 23:01:26 -0700 Subject: [PATCH 017/263] Fix Tube mode --- usermods/Tubes/controller.h | 6 ++---- usermods/Tubes/pattern.h | 9 ++++----- usermods/Tubes/virtual_strip.h | 4 ++-- wled00/FX.h | 4 ++-- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8e4f220569..81fb462037 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -265,10 +265,8 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) { - Serial.println("Nope, don't change"); + if (this->current_state.palette_id == tube_state.palette_id) return; - } this->current_state.palette_phrase = tube_state.palette_phrase; this->_load_palette(tube_state.palette_id); @@ -285,7 +283,7 @@ class PatternController : public MessageReceiver { // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return 1; // random8(4,40); + return random8(4,40); } void load_effect(TubeState &tube_state) { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index a5b2409196..d9208c8a4a 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -97,8 +97,7 @@ void bpm_palette(VirtualStrip *strip) { uint8_t beat = strip->bpm_sin16(64, 255); for (int i = 0; i < strip->num_leds; i++) { - CRGB c = strip->palette_color(i*2, strip->hue); - nscale8x3(c.r, c.g, c.b, beat-strip->hue+(i*10)); + CRGB c = strip->palette_color(i*2, strip->hue, beat-strip->hue+(i*10)); strip->leds[i] = c; } } @@ -177,13 +176,13 @@ PatternDef gPatterns[] = { {drawNoise, {MediumDuration}}, {drawNoise, {LongDuration}}, {drawNoise, {LongDuration}}, - {rainbow, {ShortDuration}}, + // {rainbow, {ShortDuration}}, {confetti, {ShortDuration}}, {confetti, {MediumDuration}}, {juggle, {ShortDuration}}, - {bpm, {ShortDuration}}, - {bpm, {MediumDuration, HighEnergy}}, + // {bpm, {ShortDuration}}, + // {bpm, {MediumDuration, HighEnergy}}, {palette_wave, {ShortDuration}}, {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index e53bf82d05..bc58e13e10 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -163,10 +163,10 @@ class VirtualStrip { } } - CRGB palette_color(uint8_t c, uint8_t offset=0) { + CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { #define WLED_COLORS #ifdef WLED_COLORS - return WS2812FX::get_palette_crgb(c + offset); + return WS2812FX::get_palette_crgb(c + offset, brightness); #else CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; return ColorFromPalette( palette, c + offset ); diff --git a/wled00/FX.h b/wled00/FX.h index 98ec9bf13d..81b092202b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -930,8 +930,8 @@ class WS2812FX { static CRGB* get_external_buffer() { return instance->external_buffer; } - static CRGB get_palette_crgb(uint16_t c) { - uint32_t color = instance->color_from_palette(c, false, true, 255); + static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { + uint32_t color = instance->color_from_palette(c, false, true, 255, brightness); return instance->col_to_crgb(color); } static void load_palette(uint8_t palette_id) { From d5f0e1f49d2559719bcd04ed73f57919184c55ef Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 3 Aug 2022 01:19:46 -0700 Subject: [PATCH 018/263] Fix palette error --- usermods/Tubes/controller.h | 1 - usermods/Tubes/pattern.h | 3 --- usermods/Tubes/virtual_strip.h | 1 + 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 81fb462037..70aec2ba6a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -292,7 +292,6 @@ class PatternController : public MessageReceiver { this->current_state.effect_params.chance == tube_state.effect_params.chance) return; - this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; this->_load_effect(tube_state.effect_params); } diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index d9208c8a4a..d8a17d13f0 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -176,13 +176,10 @@ PatternDef gPatterns[] = { {drawNoise, {MediumDuration}}, {drawNoise, {LongDuration}}, {drawNoise, {LongDuration}}, - // {rainbow, {ShortDuration}}, {confetti, {ShortDuration}}, {confetti, {MediumDuration}}, {juggle, {ShortDuration}}, - // {bpm, {ShortDuration}}, - // {bpm, {MediumDuration, HighEnergy}}, {palette_wave, {ShortDuration}}, {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index bc58e13e10..8622a72086 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -78,6 +78,7 @@ class VirtualStrip { // Interface with WLED WS2812FX::load_palette(background.palette_id); stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif } From 8d735aae59e176ac33a0303aef7d13d0c3f51fea Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 03:40:28 -0400 Subject: [PATCH 019/263] Get QuickESP comms working --- usermods/Tubes/bluetooth.h | 84 ++++------------- usermods/Tubes/controller.h | 23 ++--- usermods/Tubes/debug.h | 16 ++-- usermods/Tubes/node.h | 183 ++++++++++++++++++++++++++++++++++++ usermods/Tubes/util.h | 13 ++- wled00/wled.cpp | 2 + wled00/wled.h | 2 +- 7 files changed, 231 insertions(+), 92 deletions(-) create mode 100644 usermods/Tubes/node.h diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 198e5c8fc3..4c11d89872 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -4,22 +4,14 @@ #include #include "global_state.h" -#include "TimeSync/sync.h" -#define UPDATE_RATE 2000 // Rate at which uplink is queried for data -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost -#define CURRENT_MESH_VERSION 1 -#define MAX_CONNECTED_CLIENTS 3 +#include "node.h" -#define DATA_UPDATE_SERVICE "D00B" +// #include "TimeSync/sync.h" -typedef uint16_t MeshId; +#define MAX_CONNECTED_CLIENTS 3 -typedef struct { - MeshId id = 0; - MeshId uplinkId = 0; - uint8_t version = CURRENT_MESH_VERSION; -} MeshNodeHeader; +#define DATA_UPDATE_SERVICE "D00B" typedef struct { MeshNodeHeader header; @@ -33,14 +25,6 @@ typedef struct { NimBLEAddress address; } MeshUpdateRequest; -class MessageReceiver { - public: - - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } -}; - static TaskHandle_t xUpdaterTaskHandle; QueueHandle_t UpdaterQueue = xQueueCreate(5, sizeof(MeshUpdateRequest)); void procUpdaterTask(void* pvParameters); @@ -226,7 +210,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { bool changed = false; MeshNodeHeader ids; - char node_name[20]; uint16_t serviceUUID = 0xD00F; @@ -237,14 +220,14 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { MessageReceiver *receiver = nullptr; - Timer uplinkTimer; - Timer updateTimer; - BLEMeshNode(MessageReceiver *receiver) { this->receiver = receiver; } void advertise() { + auto service_data = std::string((char *)&ids, sizeof(ids)); + +#ifdef BLE_MESH if (!pService) return; @@ -253,8 +236,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (!pAdvertising) return; - auto service_data = std::string((char *)&ids, sizeof(ids)); - sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); // Reset the device name: // NimBLEDevice::deinit(false); @@ -264,6 +245,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->stop(); pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); pAdvertising->start(); +#endif Serial.printf("Advertising %s\n", node_name); } @@ -340,12 +322,17 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } void init() { + WiFi.mode(WIFI_AP_STA); + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + +#ifdef BLE_MESH esp_coex_preference_set(ESP_COEX_PREFER_BT); NimBLEDevice::init(std::string("Tube")); init_scanner(); init_service(); init_updater(); +#endif this->alive = true; } @@ -371,8 +358,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } void setup() { - this->reset(); - this->updateTimer.start(UPDATE_RATE); Serial.println("Mesh: ok"); } @@ -385,15 +370,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { this->init(); } - // Check the last time we heard from the uplink node - if (is_following() && this->uplinkTimer.ended()) { - Serial.println("Uplink lost"); - follow(0); - } - - if (this->ids.uplinkId && this->updateTimer.ended()) { - this->updateTimer.snooze(UPDATE_RATE); - +#ifdef BLE_MESH MeshUpdateRequest request = { .id = this->ids.uplinkId, .address = this->uplink_address @@ -401,6 +378,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { Serial.println("Update queue is full!"); } +#endif } // If any actions caused the service to change, re-advertise with new values @@ -409,13 +387,14 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { changed = false; } - if (!this->pScanner->isScanning()) { + if (this->pScanner && !this->pScanner->isScanning()) { // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. this->pScanner->start(0, nullptr, false); } } void update_node_storage(TubeState ¤t, TubeState &next) { +#ifdef BLE_MESH // Broadcast the current effect state to every connected client if (!pServer || pServer->getConnectedCount() == 0) @@ -435,34 +414,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { .next = next }; pCharacteristic->setValue(storage); - } - - void reset(MeshId id = 0) { - if (id == 0) - id = random(256, 4000); // Leave room at bottom and top of 12 bits - this->ids.id = id; - follow(0); - changed = true; - } - - void follow(MeshId uplinkId, NimBLEAdvertisedDevice* pAdvertisedDevice = NULL) { - // Following zero means you have no uplink - - // Update uplink device address - if (uplinkId && pAdvertisedDevice) - this->uplink_address = pAdvertisedDevice->getAddress(); - else - this->uplink_address = NimBLEAddress(); - - // Update uplink ID - if (this->ids.uplinkId == uplinkId) - return; - this->ids.uplinkId = uplinkId; - changed = true; - } - - bool is_following() { - return this->ids.uplinkId != 0; +#endif } // ====== CALLBACKS ======= diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 70aec2ba6a..b1fc151357 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -9,7 +9,7 @@ #include "palettes.h" #include "effects.h" #include "global_state.h" -#include "bluetooth.h" +#include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define STATUS_UPDATE_PERIOD 1000 @@ -21,8 +21,6 @@ typedef struct { uint8_t brightness; } ControllerOptions; -#define NEXT_PATTERN_TIME 53000 -#define NEXT_PALETTE_TIME 27000 #define NUM_VSTRIPS 3 @@ -77,7 +75,7 @@ class PatternController : public MessageReceiver { LEDs *led_strip; BeatController *beats; Effects *effects; - BLEMeshNode *mesh; + LightNode *node; ControllerOptions options; char key_buffer[20] = {0}; @@ -94,7 +92,8 @@ class PatternController : public MessageReceiver { this->led_strip = new LEDs(num_leds); this->beats = beats; this->effects = new Effects(); - this->mesh = new BLEMeshNode(this); + this->node = new LightNode(); + // this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED @@ -108,7 +107,7 @@ class PatternController : public MessageReceiver { void setup(bool isMaster) { - this->mesh->setup(); + this->node->setup(); this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; @@ -129,7 +128,7 @@ class PatternController : public MessageReceiver { void update() { - this->mesh->update(); + this->node->update(); this->read_keys(); @@ -208,7 +207,7 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - this->mesh->update_node_storage(this->current_state, this->next_state); + // this->node->update_node_storage(this->current_state, this->next_state); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -622,13 +621,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->mesh->update_node_storage(this->current_state, this->next_state); + // this->node->update_node_storage(this->current_state, this->next_state); } }; - - - -// What's interesting? -// c53 - clouds -// m4 - swing drift diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 8d47faa0b4..28a8397526 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,7 +1,7 @@ #pragma once #include "controller.h" -#include "bluetooth.h" +#include "node.h" std::string formatted_time(long ms) { long secs = ms / 1000; // set the seconds remaining @@ -25,14 +25,14 @@ class DebugController { public: PatternController *controller; LEDs *strip; - BLEMeshNode *mesh; + LightNode *node; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; this->strip = controller->led_strip; - this->mesh = controller->mesh; + this->node = controller->node; } void setup() @@ -44,8 +44,10 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", - this->controller->mesh->node_name, + Serial.printf("\n=== %s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", + this->controller->node->node_name, + WiFi.status(), + WiFi.channel(), WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], @@ -61,10 +63,10 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->mesh->ids.id, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->node->header.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->mesh->ids.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h new file mode 100644 index 0000000000..de7621322d --- /dev/null +++ b/usermods/Tubes/node.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#if defined ESP32 +#include +#include +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#else +#error "Unsupported platform" +#endif //ESP32 +#include + +#include "global_state.h" + +#define CURRENT_NODE_VERSION 1 +#define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS + +#define UPDATE_RATE 3000 // Rate at which uplink is queried for data +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost + +typedef uint16_t MeshId; + +typedef struct { + MeshId id = 0; + MeshId uplinkId = 0; + uint8_t version = CURRENT_NODE_VERSION; +} MeshNodeHeader; + + +void onDataSent (uint8_t* address, uint8_t status) { + Serial.printf (">> Message sent to " MACSTR ", status: %d\n", MAC2STR (address), status); +} + +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.printf (">> Received %d bytes ", len); + Serial.printf ("\"%.*s\" ", len, data); + Serial.printf ("%s", broadcast ? "broadcast" : "unicast"); + Serial.printf ("@ %d dBm ", rssi); + Serial.printf ("from " MACSTR "\n" , MAC2STR(address)); +} + + +class MessageReceiver { + public: + + virtual void onCommand(MeshId fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + + +#define NODE_STATUS_INIT 0 +#define NODE_STATUS_BROADCASTING 1 +#define NODE_STATUS_QUIET 2 + + +class LightNode { + public: + MeshNodeHeader header; + char node_name[20]; + + uint8_t status = NODE_STATUS_INIT; + bool statusChanged = false; + + Timer uplinkTimer; + Timer updateTimer; + + void onWifiConnect() { + if (this->status == NODE_STATUS_BROADCASTING) { + Serial.println("Stop Broadcasting"); + quickEspNow.stop(); + } + + Serial.println("Stop broadcasting"); + this->status = NODE_STATUS_QUIET; + } + + void onWifiDisconnect() { + if (this->status == NODE_STATUS_BROADCASTING) + return; + + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + + Serial.println("Broadcasting"); + this->status = NODE_STATUS_BROADCASTING; + } + + void onStatusChange() { + sprintf(this->node_name, + "Tube %03X:%03X", + this->header.id, + this->header.uplinkId + ); + } + + void onUplinkAlive() { + // Track the last time we received a message from our uplink + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + + void setup() { + this->reset(); + this->updateTimer.start(UPDATE_RATE); + this->status = NODE_STATUS_INIT; + this->onStatusChange(); + + quickEspNow.onDataRcvd(onDataReceived); + quickEspNow.onDataSent(onDataSent); + + Serial.println("Node: ok"); + } + + void broadcast() { + if (this->status != NODE_STATUS_BROADCASTING) { + Serial.println(">> BC NO"); + return; + } + + static unsigned int counter = 0; + static const String msg = "Hello! "; + + String message = String (msg) + " " + String (counter++); + // WiFi.disconnect (false, true); + if (!quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message.c_str (), message.length ())) { + Serial.printf (">>>>>>>>>> Message sent: %s\n", message.c_str ()); + } else { + Serial.printf (">>>>>>>>>> Message not sent\n"); + } + } + + void update() { + // Don't do anything for the first second, to allow Wifi to settle + if (millis() < 1000) + return; + + // Check the last time we heard from the uplink node + if (is_following() && this->uplinkTimer.ended()) { + Serial.println("Uplink lost"); + this->follow(0); + } + + if (this->updateTimer.ended()) { + if (WiFi.isConnected()) + this->onWifiConnect(); + else + this->onWifiDisconnect(); + + this->broadcast(); + this->updateTimer.snooze(UPDATE_RATE); + } + + if (this->statusChanged) { + this->onStatusChange(); + this->statusChanged = false; + } + } + + void reset(MeshId id = 0) { + if (id == 0) + id = random(256, 4000); // Leave room at bottom and top of 12 bits + this->header.id = id; + this->follow(0); + this->statusChanged = true; + } + + void follow(MeshId uplinkId) { + // Update uplink ID + if (this->header.uplinkId == uplinkId) + return; + + // Following zero means you have no uplink + this->header.uplinkId = uplinkId; + this->statusChanged = true; + } + + bool is_following() { + return this->header.uplinkId != 0; + } +}; \ No newline at end of file diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index 25cbc7a163..60371c52d5 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -2,6 +2,9 @@ #include "wled.h" +// Is this the tube that can control others? +#define MASTER_TUBE + uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { uint16_t rangewidth = highest - lowest; uint16_t scaledbeat = scale16( v, rangewidth ); @@ -11,6 +14,10 @@ uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { #define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) -#define __ESP32__ -#define USTD_OPTION_FS_FORCE_NO_FS -#include \ No newline at end of file +uint32_t freeMemory() { + return ESP.getFreeHeap(); +} + +// #define __ESP32__ +// #define USTD_OPTION_FS_FORCE_NO_FS +// #include \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 54c723c80a..203357f43e 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -459,6 +459,8 @@ void WLED::initAP(bool resetAP) strcpy_P(apPass, PSTR(DEFAULT_AP_PASS)); DEBUG_PRINT(F("Opening access point ")); DEBUG_PRINTLN(apSSID); + DEBUG_PRINT(F("Password ")); + DEBUG_PRINTLN(apPass); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); diff --git a/wled00/wled.h b/wled00/wled.h index deeac1260f..7194394626 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -49,7 +49,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -//#define WLED_DEBUG +#define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS From 0bf1d3e9d15d5b8ad6cca6383028bf6d43a9b268 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 04:00:24 -0400 Subject: [PATCH 020/263] auto-configure the network --- usermods/Tubes/node.h | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index de7621322d..f8ba49cf83 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -62,11 +62,18 @@ class LightNode { char node_name[20]; uint8_t status = NODE_STATUS_INIT; - bool statusChanged = false; + bool meshChanged = false; Timer uplinkTimer; Timer updateTimer; + void configure_ap() { + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + strcpy(apSSID, ""); + apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; + } + void onWifiConnect() { if (this->status == NODE_STATUS_BROADCASTING) { Serial.println("Stop Broadcasting"); @@ -89,7 +96,7 @@ class LightNode { this->status = NODE_STATUS_BROADCASTING; } - void onStatusChange() { + void onMeshChange() { sprintf(this->node_name, "Tube %03X:%03X", this->header.id, @@ -103,14 +110,17 @@ class LightNode { } void setup() { - this->reset(); + configure_ap(); + this->updateTimer.start(UPDATE_RATE); this->status = NODE_STATUS_INIT; - this->onStatusChange(); + + this->reset(); + this->onMeshChange(); quickEspNow.onDataRcvd(onDataReceived); quickEspNow.onDataSent(onDataSent); - + Serial.println("Node: ok"); } @@ -143,6 +153,11 @@ class LightNode { this->follow(0); } + if (this->meshChanged) { + this->onMeshChange(); + this->meshChanged = false; + } + if (this->updateTimer.ended()) { if (WiFi.isConnected()) this->onWifiConnect(); @@ -152,11 +167,6 @@ class LightNode { this->broadcast(); this->updateTimer.snooze(UPDATE_RATE); } - - if (this->statusChanged) { - this->onStatusChange(); - this->statusChanged = false; - } } void reset(MeshId id = 0) { @@ -164,7 +174,7 @@ class LightNode { id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; this->follow(0); - this->statusChanged = true; + this->meshChanged = true; } void follow(MeshId uplinkId) { @@ -174,7 +184,7 @@ class LightNode { // Following zero means you have no uplink this->header.uplinkId = uplinkId; - this->statusChanged = true; + this->meshChanged = true; } bool is_following() { From db3bfe2f69e1aa7b649aa8fff85971e60f2502ce Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 16:32:51 -0400 Subject: [PATCH 021/263] Get syncing working & Speed up color changes --- usermods/Tubes/controller.h | 14 ++-- usermods/Tubes/node.h | 151 ++++++++++++++++++++++++++---------- wled00/wled.h | 2 +- 3 files changed, 119 insertions(+), 48 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b1fc151357..a358bc1daa 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -14,6 +14,8 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define STATUS_UPDATE_PERIOD 1000 +#define MIN_COLOR_CHANGE_PHRASES 2 // 4 +#define MAX_COLOR_CHANGE_PHRASES 4 // 40 typedef struct { @@ -92,7 +94,7 @@ class PatternController : public MessageReceiver { this->led_strip = new LEDs(num_leds); this->beats = beats; this->effects = new Effects(); - this->node = new LightNode(); + this->node = new LightNode(this); // this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { @@ -128,7 +130,7 @@ class PatternController : public MessageReceiver { void update() { - this->node->update(); + this->node->update(this->current_state, this->next_state); this->read_keys(); @@ -282,7 +284,7 @@ class PatternController : public MessageReceiver { // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return random8(4,40); + return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); } void load_effect(TubeState &tube_state) { @@ -411,10 +413,8 @@ class PatternController : public MessageReceiver { addFlash(); } - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - if (fromId) { - Serial.printf("From %03X: ", fromId); - } + virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { + Serial.printf("From %03X/%03X: ", header->id, header->uplinkId); switch (command) { case COMMAND_RESET: diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f8ba49cf83..139bf1407a 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -17,8 +17,9 @@ #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define UPDATE_RATE 3000 // Rate at which uplink is queried for data -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +#define BROADCAST_RATE 3000 // Rate at which to broadcast as leader +#define REBROADCAST_RATE 7000 // Rate at which to re-broadcast as follower +#define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost typedef uint16_t MeshId; @@ -28,26 +29,20 @@ typedef struct { uint8_t version = CURRENT_NODE_VERSION; } MeshNodeHeader; +typedef struct { + MeshNodeHeader header; + TubeState current; + TubeState next; +} NodeMessage; -void onDataSent (uint8_t* address, uint8_t status) { - Serial.printf (">> Message sent to " MACSTR ", status: %d\n", MAC2STR (address), status); -} - -void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - Serial.printf (">> Received %d bytes ", len); - Serial.printf ("\"%.*s\" ", len, data); - Serial.printf ("%s", broadcast ? "broadcast" : "unicast"); - Serial.printf ("@ %d dBm ", rssi); - Serial.printf ("from " MACSTR "\n" , MAC2STR(address)); -} +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); class MessageReceiver { public: - - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } + virtual void onCommand(CommandId command, MeshNodeHeader* header, void *data) { + // Abstract: subclasses must define + } }; @@ -58,14 +53,24 @@ class MessageReceiver { class LightNode { public: + static LightNode* instance; + + MessageReceiver *receiver; MeshNodeHeader header; + char node_name[20]; uint8_t status = NODE_STATUS_INIT; bool meshChanged = false; Timer uplinkTimer; - Timer updateTimer; + Timer broadcastTimer; + + LightNode(MessageReceiver *receiver) { + LightNode::instance = this; + + this->receiver = receiver; + } void configure_ap() { strcpy(clientSSID, ""); @@ -104,45 +109,106 @@ class LightNode { ); } - void onUplinkAlive() { - // Track the last time we received a message from our uplink - this->uplinkTimer.start(UPLINK_TIMEOUT); + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + // Ignore this packet if it couldn't be a mesh report. + if (len != sizeof(NodeMessage)) + return; + + NodeMessage* message = (NodeMessage*)data; + // Serial.printf(">> Received %db ", len); + // Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + // Serial.printf("at " MACSTR, MAC2STR(address)); + // Serial.printf("@ %ddBm: ", rssi); + + // Ignore this packet if wrong version + if (message->header.version != this->header.version) { + // Serial.printf(" header.id <= this->header.uplinkId) { + // Serial.printf(" ignoring\n", rssi); + return; + } + + // Serial.printf(" listening\n", rssi); + this->onPeerPing(&message->header); + + // Execute the received command + MeshId fromId = message->header.uplinkId; + if (!fromId) fromId = message->header.id; + + this->receiver->onCommand( + COMMAND_UPDATE, + &message->header, + &message->current + ); + this->receiver->onCommand( + COMMAND_NEXT, + &message->header, + &message->next + ); + } + + void onPeerPing(MeshNodeHeader* node) { + if (node->id == this->header.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + if (node->id > this->header.uplinkId && node->id > this->header.id) { + Serial.printf("Following %03X:%03X\n", + node->id, + node->uplinkId + ); + + this->follow(node->id); + } + + if (node->id == this->header.uplinkId) { + this->uplinkTimer.start(UPLINK_TIMEOUT); + } } void setup() { configure_ap(); - this->updateTimer.start(UPDATE_RATE); + this->broadcastTimer.start(BROADCAST_RATE); this->status = NODE_STATUS_INIT; this->reset(); this->onMeshChange(); quickEspNow.onDataRcvd(onDataReceived); - quickEspNow.onDataSent(onDataSent); Serial.println("Node: ok"); } - void broadcast() { - if (this->status != NODE_STATUS_BROADCASTING) { - Serial.println(">> BC NO"); + void broadcast(TubeState ¤t, TubeState &next) { + // Don't broadcast if not in broadcast mode + if (this->status != NODE_STATUS_BROADCASTING) return; - } - static unsigned int counter = 0; - static const String msg = "Hello! "; - - String message = String (msg) + " " + String (counter++); - // WiFi.disconnect (false, true); - if (!quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message.c_str (), message.length ())) { - Serial.printf (">>>>>>>>>> Message sent: %s\n", message.c_str ()); + NodeMessage message = { + .header = this->header, + .current = current, + .next = next, + }; + + auto err = quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, + (uint8_t*)&message, sizeof(message)); + if (err) + Serial.printf(">> Broadcast error %d\n", err); + + if (this->is_following()) { + this->broadcastTimer.snooze(REBROADCAST_RATE); } else { - Serial.printf (">>>>>>>>>> Message not sent\n"); + this->broadcastTimer.snooze(BROADCAST_RATE); } } - void update() { + void update(TubeState ¤t, TubeState &next) { // Don't do anything for the first second, to allow Wifi to settle if (millis() < 1000) return; @@ -158,14 +224,13 @@ class LightNode { this->meshChanged = false; } - if (this->updateTimer.ended()) { + if (this->broadcastTimer.ended()) { if (WiFi.isConnected()) this->onWifiConnect(); else this->onWifiDisconnect(); - this->broadcast(); - this->updateTimer.snooze(UPDATE_RATE); + this->broadcast(current, next); } } @@ -190,4 +255,10 @@ class LightNode { bool is_following() { return this->header.uplinkId != 0; } -}; \ No newline at end of file +}; + +LightNode* LightNode::instance = nullptr; + +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + LightNode::instance->onPeerData(address, data, len, rssi, broadcast); +} diff --git a/wled00/wled.h b/wled00/wled.h index 7194394626..4464461254 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -49,7 +49,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -#define WLED_DEBUG +// #define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS From c19d1d5693ae1ccb5937a8e0457b643ac2dc1e0e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 17:35:54 -0400 Subject: [PATCH 022/263] Restore tube tech & finish --- usermods/Tubes/virtual_strip.h | 3 ++- wled00/FX.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 8622a72086..5b12411e42 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -8,7 +8,7 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -//#define TESTING_PATTERNS +// #define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -77,6 +77,7 @@ class VirtualStrip { #ifdef MASTER_TUBE // Interface with WLED WS2812FX::load_palette(background.palette_id); + WS2812FX::load_pattern(FX_MODE_EXTERNAL); stateChanged = true; stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif diff --git a/wled00/FX.h b/wled00/FX.h index 81b092202b..d7f5928d99 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -940,6 +940,11 @@ class WS2812FX { seg.palette = palette_id; } } + static void load_pattern(uint8_t pattern_id) { + for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + instance->setMode(i, pattern_id); + } + } static WS2812FX* get_strip() { return instance; } From fd1acfb20be282ceaa9cfc1e1a579585dda44fab Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 23:55:48 -0400 Subject: [PATCH 023/263] Turn keyboard commands back on. --- usermods/Tubes/controller.h | 9 +++++++-- wled00/wled_serial.cpp | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a358bc1daa..7cff1beab5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -594,7 +594,7 @@ class PatternController : public MessageReceiver { case 'g': for (int i=0; i< 10; i++) addGlitter(); - break; + return; case '?': Serial.println(F("b###.# - set bpm")); @@ -608,6 +608,11 @@ class PatternController : public MessageReceiver { Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); + return; + + default: + Serial.println("dunno?"); + return; } } @@ -621,7 +626,7 @@ class PatternController : public MessageReceiver { } void update_next() { - // this->node->update_node_storage(this->current_state, this->next_state); + this->node->broadcast(this->current_state, this->next_state); } }; diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index 2fafaceb10..e092da6e2d 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -36,6 +36,7 @@ void updateBaudRate(uint32_t rate){ void handleSerial() { + return; if (pinManager.isPinAllocated(3)) return; #ifdef WLED_ENABLE_ADALIGHT From e9090cbdc4af3abb712f16bc9b12de174f4a4caf Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 7 Aug 2022 01:19:28 -0400 Subject: [PATCH 024/263] Move overlay effects to a Usermod overlay so they work on any pattern --- usermods/Tubes/Tubes.h | 7 ++++ usermods/Tubes/controller.h | 3 +- usermods/Tubes/effects.h | 4 +- usermods/Tubes/particle.h | 73 ++++++++++++++++++++----------------- wled00/FX.h | 8 ++++ 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 303666c9bb..81aebeab47 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -57,6 +57,13 @@ class TubesUsermod : public Usermod { // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us } + + void handleOverlayDraw() + { + // Draw effects layers over whatever WLED is doing. + WS2812FX* leds = &strip; + controller.effects->draw(leds); + } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 7cff1beab5..bde3b66906 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -406,11 +406,10 @@ class PatternController : public MessageReceiver { } this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); - this->effects->draw(this->led_strip->leds, this->num_leds); } virtual void acknowledge() { - addFlash(); + addFlash(CRGB::Green); } virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 147196c334..47308197cd 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -112,11 +112,11 @@ class Effects { } } - void draw(CRGB strip[], uint8_t num_leds) { + void draw(WS2812FX* leds) { uint8_t len = numParticles; for (uint8_t i=0; idrawFn(particle, strip, num_leds); + particle->drawFn(particle, leds); } } diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d31e4185d7..bbe6118475 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -9,10 +9,10 @@ class Particle; -typedef void (*ParticleFn)(Particle *particle, CRGB strip[], uint8_t num_leds); +typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); -extern void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds); -extern void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds); +extern void drawPoint(Particle *particle, WS2812FX* leds); +extern void drawFlash(Particle *particle, WS2812FX* leds); class Particle { @@ -96,59 +96,62 @@ class Particle { return CRGB(r,g,b); } - void draw_with_pen(CRGB strip[], int pos, CRGB color) { + void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { CRGB new_color; - + switch (this->pen) { case Draw: - strip[pos] = color; + new_color = color; break; case Blend: - strip[pos] |= color; + new_color = WS2812FX::get_crgb(pos) | color; break; case Erase: - strip[pos] &= color; + new_color = WS2812FX::get_crgb(pos) & color; break; case Invert: - strip[pos] = -strip[pos]; + new_color = -WS2812FX::get_crgb(pos); break; case Brighten: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - strip[pos] += new_color; + new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); break; } case Darken: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - strip[pos] -= new_color; + new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); break; } case Flicker: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - if (millis() % 2) - strip[pos] -= new_color; - else - strip[pos] += new_color; + if (millis() % 2) { + new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); + } else { + new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); + } break; } case White: - strip[pos] = CRGB::White; + new_color = CRGB::White; break; case Black: - strip[pos] = CRGB::Black; + new_color = CRGB::Black; break; + default: + // Unknown pen + return; } + + WS2812FX::set_crgb(pos, new_color); } }; @@ -182,55 +185,57 @@ void addParticle(Particle *particle) { particles[numParticles++] = particle; } -void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawFlash(Particle *particle, WS2812FX* leds) { + auto num_leds = leds->getLengthTotal(); uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); for (int pos = 0; pos < num_leds; pos++) { - particle->draw_with_pen(strip, pos, c); + particle->draw_with_pen(leds, pos, c); } } -void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawPoint(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); - particle->draw_with_pen(strip, pos, c); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); + particle->draw_with_pen(leds, pos, c); } -void drawRadius(Particle *particle, CRGB strip[], uint8_t num_leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { +void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; nscale8(&c, 1, bright); uint8_t y = pos - i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(strip, y, c); + particle->draw_with_pen(leds, y, c); if (i == 0) continue; y = pos + i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(strip, y, c); + particle->draw_with_pen(leds, y, c); } } -void drawPop(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawPop(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); uint8_t radius = scale16((sin16(age_frac/2) - 32768) * 2, 8); - drawRadius(particle, strip, num_leds, pos, radius, c); + drawRadius(particle, leds, pos, radius, c); } -void drawBeatbox(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawBeatbox(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); uint8_t radius = 5; - drawRadius(particle, strip, num_leds, pos, radius, c, false); + drawRadius(particle, leds, pos, radius, c, false); } diff --git a/wled00/FX.h b/wled00/FX.h index d7f5928d99..64efbdc5f5 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -949,6 +949,14 @@ class WS2812FX { return instance; } + static CRGB get_crgb(uint32_t pos) { + return instance->col_to_crgb(instance->getPixelColor(pos)); + } + static void set_crgb(uint32_t pos, CRGB color) { + instance->setPixelColor(pos, instance->crgb_to_col(color)); + } + + inline bool hasWhiteChannel(void) {return _hasWhiteChannel;} inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;} }; From a2f5b11ac4a123b24ff5871aec53b910827a37e0 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 8 Aug 2022 04:40:07 -0400 Subject: [PATCH 025/263] Update node mesh code for less chattiness and quicker startup --- usermods/Tubes/controller.h | 25 ++- usermods/Tubes/global_state.h | 1 - usermods/Tubes/node.h | 291 +++++++++++++++++++++------------- 3 files changed, 194 insertions(+), 123 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index bde3b66906..51db4d45f5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -412,9 +412,7 @@ class PatternController : public MessageReceiver { addFlash(CRGB::Green); } - virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { - Serial.printf("From %03X/%03X: ", header->id, header->uplinkId); - + virtual void onCommand(CommandId command, void *data) { switch (command) { case COMMAND_RESET: Serial.println(F("reset")); @@ -433,22 +431,17 @@ class PatternController : public MessageReceiver { return; } - case COMMAND_NEXT: { - Serial.print(F(" next ")); - - memcpy(&this->next_state, data, sizeof(TubeState)); - this->next_state.print(); - Serial.println(F(" (obeying)")); - return; - } - case COMMAND_UPDATE: { Serial.print(F(" update ")); + auto update_data = (NodeUpdate*)data; + TubeState state; - memcpy(&state, data, sizeof(TubeState)); + memcpy(&state, &update_data->current, sizeof(TubeState)); + memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); state.print(); - Serial.println(F(" (obeying)")); + this->next_state.print(); + Serial.println(); // Catch up to this state this->load_pattern(state); @@ -460,7 +453,7 @@ class PatternController : public MessageReceiver { } Serial.print(F("UNKNOWN ")); - Serial.print(command, HEX); + Serial.println(command, HEX); } void read_keys() { @@ -625,7 +618,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->node->broadcast(this->current_state, this->next_state); + this->node->update_status(this->current_state, this->next_state); } }; diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index d75b940f83..bf4ba4f2e5 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,7 +57,6 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_NEXT = 0x30; const static CommandId COMMAND_RESET = 0xF0; const static CommandId COMMAND_BRIGHTNESS = 0x80; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 139bf1407a..f5d0d32309 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -14,12 +14,15 @@ #include "global_state.h" +// #define NODE_DEBUGGING +#define TESTING_NODE_ID 100 + #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define BROADCAST_RATE 3000 // Rate at which to broadcast as leader -#define REBROADCAST_RATE 7000 // Rate at which to re-broadcast as follower +#define BROADCAST_RATE 2000 // Rate at which to broadcast state updates as leader #define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost +#define REBROADCAST_TIME 15000 // Time at which followers are presumed re-uplinked typedef uint16_t MeshId; @@ -30,9 +33,16 @@ typedef struct { } MeshNodeHeader; typedef struct { - MeshNodeHeader header; TubeState current; TubeState next; +} NodeUpdate; + +typedef struct { + MeshNodeHeader header; + CommandId command; + union { + NodeUpdate update; + } data; } NodeMessage; void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); @@ -40,15 +50,16 @@ void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rs class MessageReceiver { public: - virtual void onCommand(CommandId command, MeshNodeHeader* header, void *data) { + virtual void onCommand(CommandId command, void *data) { // Abstract: subclasses must define } }; - -#define NODE_STATUS_INIT 0 -#define NODE_STATUS_BROADCASTING 1 -#define NODE_STATUS_QUIET 2 +typedef enum{ + NODE_STATUS_QUIET=0, + NODE_STATUS_STARTING=1, + NODE_STATUS_STARTED=2, +} NodeStatus; class LightNode { @@ -57,14 +68,15 @@ class LightNode { MessageReceiver *receiver; MeshNodeHeader header; + NodeStatus status = NODE_STATUS_QUIET; - char node_name[20]; + bool meshStarted = false; - uint8_t status = NODE_STATUS_INIT; - bool meshChanged = false; + char node_name[20]; - Timer uplinkTimer; - Timer broadcastTimer; + Timer uplinkTimer; // When this timer ends, assume uplink is lost. + Timer broadcastTimer; // When this timer ends, send a status update + Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink LightNode(MessageReceiver *receiver) { LightNode::instance = this; @@ -80,25 +92,26 @@ class LightNode { } void onWifiConnect() { - if (this->status == NODE_STATUS_BROADCASTING) { - Serial.println("Stop Broadcasting"); + if (this->meshStarted) { + Serial.println("WiFi connected: stop broadcasting"); quickEspNow.stop(); + this->meshStarted = false; } - - Serial.println("Stop broadcasting"); + this->status = NODE_STATUS_QUIET; } void onWifiDisconnect() { - if (this->status == NODE_STATUS_BROADCASTING) - return; - - WiFi.mode (WIFI_MODE_STA); - WiFi.disconnect(false, true); - quickEspNow.begin(1, WIFI_IF_STA); + if (!this->meshStarted) { + Serial.println("WiFi disconnected: start broadcasting"); + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + this->meshStarted = true; + } - Serial.println("Broadcasting"); - this->status = NODE_STATUS_BROADCASTING; + if (this->status == NODE_STATUS_QUIET) + this->status = NODE_STATUS_STARTING; } void onMeshChange() { @@ -109,147 +122,213 @@ class LightNode { ); } + void onPeerPing(MeshNodeHeader* node) { + // When receiving a message, if the IDs match, it's a conflict + // Reset to create a new ID. + if (node->id == this->header.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + // If the message arrives from a higher ID, switch into follower mode + if (node->id > this->header.uplinkId && node->id > this->header.id) { + if (this->header.id != TESTING_NODE_ID || node->id < 0x800) + this->follow(node); + } + + // If the message arrived from our uplink, track that we're still linked. + if (node->id == this->header.uplinkId) { + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + + // If a message indicates that another node is following this one, + // enter or continue re-broadcasting mode (unless already LEAD) + if (node->uplinkId == this->header.id) { + Serial.printf("%03X/%03X is following me\n", node->id, node->uplinkId); + this->rebroadcastTimer.start(REBROADCAST_TIME); + } + } + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - // Ignore this packet if it couldn't be a mesh report. + // Ignore this message if it isn't a valid message payload. if (len != sizeof(NodeMessage)) return; NodeMessage* message = (NodeMessage*)data; - // Serial.printf(">> Received %db ", len); - // Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); - // Serial.printf("at " MACSTR, MAC2STR(address)); - // Serial.printf("@ %ddBm: ", rssi); - - // Ignore this packet if wrong version +#ifdef NODE_DEBUGGING + Serial.printf(">> Received %db ", len); + Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + Serial.printf("at " MACSTR, MAC2STR(address)); + Serial.printf("@ %ddBm: ", rssi); +#endif + + // Ignore this message if it's the wrong version. if (message->header.version != this->header.version) { - // Serial.printf(" header.id <= this->header.uplinkId) { - // Serial.printf(" ignoring\n", rssi); + // Track that another node exists, updating this node's understanding of the mesh. + this->onPeerPing(&message->header); + + // Ignore this message if not from my uplink + if (message->header.id != this->header.uplinkId) { +#ifdef NODE_DEBUGGING + Serial.printf(" ignoring\n"); +#endif return; } - // Serial.printf(" listening\n", rssi); - this->onPeerPing(&message->header); +#ifdef NODE_DEBUGGING + Serial.printf(" listening\n"); +#endif // Execute the received command - MeshId fromId = message->header.uplinkId; - if (!fromId) fromId = message->header.id; - - this->receiver->onCommand( - COMMAND_UPDATE, - &message->header, - &message->current - ); + Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); this->receiver->onCommand( - COMMAND_NEXT, - &message->header, - &message->next + message->command, + &message->data ); - } - void onPeerPing(MeshNodeHeader* node) { - if (node->id == this->header.id) { - Serial.println("Detected an ID conflict."); - this->reset(); - } - - if (node->id > this->header.uplinkId && node->id > this->header.id) { - Serial.printf("Following %03X:%03X\n", - node->id, - node->uplinkId - ); - - this->follow(node->id); + // Re-broadcast the message if appropriate + if (!this->rebroadcastTimer.ended()) { + message->header = this->header; + this->broadcast(message, true); } + } - if (node->id == this->header.uplinkId) { - this->uplinkTimer.start(UPLINK_TIMEOUT); - } + void broadcast(NodeMessage *message, bool is_rebroadcast=false) { + // Don't broadcast anything if this node isn't active. + if (this->status != NODE_STATUS_STARTED) + return; + + auto err = quickEspNow.send( + ESPNOW_BROADCAST_ADDRESS, + (uint8_t*)message, sizeof(*message) + ); + if (err) + Serial.printf(">> Broadcast error %d\n", err); } void setup() { configure_ap(); - this->broadcastTimer.start(BROADCAST_RATE); - this->status = NODE_STATUS_INIT; - +#ifdef NODE_DEBUGGING + this->reset(TESTING_NODE_ID); +#else this->reset(); - this->onMeshChange(); +#endif + this->broadcastTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); Serial.println("Node: ok"); } - void broadcast(TubeState ¤t, TubeState &next) { - // Don't broadcast if not in broadcast mode - if (this->status != NODE_STATUS_BROADCASTING) + void set_timer() { + // Timer in QUIET mode determines how often we'll check WiFi status + if (this->status == NODE_STATUS_QUIET) { + this->broadcastTimer.start(BROADCAST_RATE); + this->rebroadcastTimer.stop(); return; + } - NodeMessage message = { - .header = this->header, - .current = current, - .next = next, - }; - - auto err = quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, - (uint8_t*)&message, sizeof(message)); - if (err) - Serial.printf(">> Broadcast error %d\n", err); + // Initial timer: wait for a bit before trying to broadcast. + // If this node's ID is high, it's more likely to be the leader, so wait less. + if (this->status == NODE_STATUS_STARTING) { + auto next_time = 4000 - this->header.id/2; + this->broadcastTimer.start(next_time); + this->rebroadcastTimer.start(REBROADCAST_TIME); + return; + } + // If following, only rebroadcast every 5 cycles if (this->is_following()) { - this->broadcastTimer.snooze(REBROADCAST_RATE); - } else { - this->broadcastTimer.snooze(BROADCAST_RATE); + auto next_time = 5 * BROADCAST_RATE; + + // Randomize a bit so not everyone is rebroadcasting at the same time + next_time += random(0, 2000) - 1000; + + this->broadcastTimer.start(next_time); + return; } + + this->broadcastTimer.start(BROADCAST_RATE); } void update(TubeState ¤t, TubeState &next) { - // Don't do anything for the first second, to allow Wifi to settle - if (millis() < 1000) - return; - // Check the last time we heard from the uplink node if (is_following() && this->uplinkTimer.ended()) { - Serial.println("Uplink lost"); - this->follow(0); - } - - if (this->meshChanged) { - this->onMeshChange(); - this->meshChanged = false; + this->follow(NULL); } if (this->broadcastTimer.ended()) { + // The broadcast timer doubles as a timer for startup delay + // Once the initial timer has ended, mark this node as started + if (this->status == NODE_STATUS_STARTING) + this->status = NODE_STATUS_STARTED; + + // Check WiFi status and update node status if wifi changed if (WiFi.isConnected()) this->onWifiConnect(); else this->onWifiDisconnect(); - this->broadcast(current, next); + // Set the next time and reset the rebroadcast monitoring + this->update_status(current, next); + this->set_timer(); } } + void update_status(TubeState ¤t, TubeState &next) { + // Broadcast (or rebroadcast) the current state + NodeMessage message = { + .header = this->header, + .command = COMMAND_UPDATE, + .data = { + .update = { + .current = current, + .next = next + } + } + }; + this->broadcast(&message); + } + void reset(MeshId id = 0) { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; - this->follow(0); - this->meshChanged = true; + this->status = NODE_STATUS_STARTING; + this->follow(NULL); + this->onMeshChange(); } - void follow(MeshId uplinkId) { - // Update uplink ID - if (this->header.uplinkId == uplinkId) + void follow(MeshNodeHeader* node) { + if (node == NULL) { + if (this->header.uplinkId != 0) { + Serial.println("Uplink lost"); + } + + // Unfollow: following zero means you have no uplink + this->header.uplinkId = 0; + this->onMeshChange(); + return; + } + + // Already following? ignore + if (this->header.uplinkId == node->id) return; - // Following zero means you have no uplink - this->header.uplinkId = uplinkId; - this->meshChanged = true; + // Follow + Serial.printf("Following %03X:%03X\n", + node->id, + node->uplinkId + ); + this->header.uplinkId = node->id; + this->onMeshChange(); } bool is_following() { From 7bc6497e6aa2a0154f04e859c54e71749b301c8b Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 4 Jul 2022 20:41:02 -0700 Subject: [PATCH 026/263] Initial port of Tubes - palettes and noise functions --- wled00/FX.cpp | 46 + wled00/palettes.h | 2119 +++++++++++++++++++++++++++++++++++---------- wled00/wled.h | 4 + 3 files changed, 1733 insertions(+), 436 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 540a1ad3e3..301c9d9039 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4499,6 +4499,7 @@ uint16_t mode_aurora(void) { return FRAMETIME; } +<<<<<<< HEAD static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;sx=24,pal=50,1d"; // WLED-SR effects @@ -7513,3 +7514,48 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); } +======= + + + + +#define MAX_VIRTUAL_LEDS 150 +uint8_t noise[MAX_VIRTUAL_LEDS]; + +void fillnoise8(uint32_t frame, uint8_t num_leds) { + uint16_t scale = 17; + uint8_t dataSmoothing = 240; + + for (int i = 0; i < num_leds; i++) { + uint8_t data = inoise8(i * scale, frame>>2); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + data = qsub8(data,16); + data = qadd8(data,scale8(data,39)); + + uint8_t olddata = noise[i]; + uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); + noise[i] = newdata; + } +} + +uint16_t WS2812FX::mode_tubes_moise(void) { + uint16_t pixelLen = SEGLEN > MAX_VIRTUAL_LEDS ? MAX_VIRTUAL_LEDS : SEGLEN; + // uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 + // if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + // uint32_t* pixels = reinterpret_cast(SEGENV.data); + uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); + uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; + + // generate noise data + fillnoise8(now>>4, pixelLen); + + uint16_t offset = 0; + for (int i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(noise[i], true, PALETTE_SOLID_WRAP, 0)); + } + + return FRAMETIME; +} +>>>>>>> 3b8e5a2f (Initial port of Tubes - palettes and noise functions) diff --git a/wled00/palettes.h b/wled00/palettes.h index 5e524059d3..5bb34f13e7 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -13,7 +13,17 @@ #ifndef PalettesWLED_h #define PalettesWLED_h -const byte ib_jul01_gp[] PROGMEM = { +// Redefine FastLED's gradient palette declaration: +#define DEFINE_PALETTE(X) const byte X[] PROGMEM = + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +// Gradient palette "ib_jul01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 bytes of program space. + +DEFINE_PALETTE(ib_jul01_gp) { 0, 194, 1, 1, 94, 1, 29, 18, 132, 57,131, 28, @@ -24,20 +34,19 @@ const byte ib_jul01_gp[] PROGMEM = { // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_vintage_57_gp[] PROGMEM = { +DEFINE_PALETTE( es_vintage_57_gp ) { 0, 2, 1, 1, 53, 18, 1, 0, 104, 69, 29, 1, 153, 167,135, 10, 255, 46, 56, 4}; - // Gradient palette "es_vintage_01_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte es_vintage_01_gp[] PROGMEM = { +DEFINE_PALETTE( es_vintage_01_gp ) { 0, 4, 1, 1, 51, 16, 0, 1, 76, 97,104, 3, @@ -47,92 +56,124 @@ const byte es_vintage_01_gp[] PROGMEM = { 229, 4, 1, 1, 255, 4, 1, 1}; - // Gradient palette "es_rivendell_15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_rivendell_15_gp[] PROGMEM = { +DEFINE_PALETTE( es_rivendell_15_gp ) { 0, 1, 14, 5, 101, 16, 36, 14, 165, 56, 68, 30, 242, 150,156, 99, 255, 150,156, 99}; - // Gradient palette "rgi_15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -// Edited to be brighter - -const byte rgi_15_gp[] PROGMEM = { - 0, 4, 1, 70, - 31, 55, 1, 30, - 63, 255, 4, 7, - 95, 59, 2, 29, - 127, 11, 3, 50, - 159, 39, 8, 60, - 191, 112, 19, 40, - 223, 78, 11, 39, - 255, 29, 8, 59}; +DEFINE_PALETTE( rgi_15_gp ) { + 0, 4, 1, 31, + 31, 55, 1, 16, + 63, 197, 3, 7, + 95, 59, 2, 17, + 127, 6, 2, 34, + 159, 39, 6, 33, + 191, 112, 13, 32, + 223, 56, 9, 35, + 255, 22, 6, 38}; // Gradient palette "retro2_16_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 8 bytes of program space. -const byte retro2_16_gp[] PROGMEM = { +DEFINE_PALETTE( retro2_16_gp ) { 0, 188,135, 1, 255, 46, 7, 1}; - // Gradient palette "Analogous_1_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Analogous_1_gp[] PROGMEM = { +DEFINE_PALETTE( Analogous_1_gp ) { 0, 3, 0,255, 63, 23, 0,255, 127, 67, 0,255, 191, 142, 0, 45, 255, 255, 0, 0}; - // Gradient palette "es_pinksplash_08_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_pinksplash_08_gp[] PROGMEM = { +DEFINE_PALETTE( es_pinksplash_08_gp ) { 0, 126, 11,255, 127, 197, 1, 22, 175, 210,157,172, 221, 157, 3,112, 255, 157, 3,112}; +// Gradient palette "es_pinksplash_07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_pinksplash_07_gp ) { + 0, 229, 1, 1, + 61, 242, 4, 63, + 101, 255, 12,255, + 127, 249, 81,252, + 153, 255, 11,235, + 193, 244, 5, 68, + 255, 232, 1, 5}; + +// Gradient palette "Coral_reef_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Coral_reef.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( Coral_reef_gp ) { + 0, 40,199,197, + 50, 10,152,155, + 96, 1,111,120, + 96, 43,127,162, + 139, 10, 73,111, + 255, 1, 34, 71}; + +// Gradient palette "es_ocean_breeze_068_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_068.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_068_gp ) { + 0, 100,156,153, + 51, 1, 99,137, + 101, 1, 68, 84, + 104, 35,142,168, + 178, 0, 63,117, + 255, 1, 10, 10}; // Gradient palette "es_ocean_breeze_036_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 16 bytes of program space. -const byte es_ocean_breeze_036_gp[] PROGMEM = { +DEFINE_PALETTE( es_ocean_breeze_036_gp ) { 0, 1, 6, 7, 89, 1, 99,111, 153, 144,209,255, 255, 0, 73, 82}; - // Gradient palette "departure_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 88 bytes of program space. -const byte departure_gp[] PROGMEM = { +DEFINE_PALETTE( departure_gp ) { 0, 8, 3, 0, 42, 23, 7, 0, 63, 75, 38, 6, @@ -146,13 +187,12 @@ const byte departure_gp[] PROGMEM = { 212, 0, 55, 0, 255, 0, 55, 0}; - // Gradient palette "es_landscape_64_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -const byte es_landscape_64_gp[] PROGMEM = { +DEFINE_PALETTE( es_landscape_64_gp ) { 0, 0, 0, 0, 37, 2, 25, 1, 76, 15,115, 5, @@ -163,13 +203,12 @@ const byte es_landscape_64_gp[] PROGMEM = { 204, 59,117,250, 255, 1, 37,192}; - // Gradient palette "es_landscape_33_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte es_landscape_33_gp[] PROGMEM = { +DEFINE_PALETTE( es_landscape_33_gp ) { 0, 1, 5, 0, 19, 32, 23, 1, 38, 161, 55, 1, @@ -177,13 +216,12 @@ const byte es_landscape_33_gp[] PROGMEM = { 66, 39,142, 74, 255, 1, 4, 1}; - // Gradient palette "rainbowsherbet_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte rainbowsherbet_gp[] PROGMEM = { +DEFINE_PALETTE( rainbowsherbet_gp ) { 0, 255, 33, 4, 43, 255, 68, 25, 86, 255, 7, 25, @@ -192,13 +230,12 @@ const byte rainbowsherbet_gp[] PROGMEM = { 209, 42,255, 22, 255, 87,255, 65}; - // Gradient palette "gr65_hult_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte gr65_hult_gp[] PROGMEM = { +DEFINE_PALETTE( gr65_hult_gp ) { 0, 247,176,247, 48, 255,136,255, 89, 220, 29,226, @@ -206,13 +243,12 @@ const byte gr65_hult_gp[] PROGMEM = { 216, 1,124,109, 255, 1,124,109}; - // Gradient palette "gr64_hult_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte gr64_hult_gp[] PROGMEM = { +DEFINE_PALETTE( gr64_hult_gp ) { 0, 1,124,109, 66, 1, 93, 79, 104, 52, 65, 1, @@ -222,13 +258,12 @@ const byte gr64_hult_gp[] PROGMEM = { 239, 0, 55, 45, 255, 0, 55, 45}; - // Gradient palette "GMT_drywet_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte GMT_drywet_gp[] PROGMEM = { +DEFINE_PALETTE( GMT_drywet_gp ) { 0, 47, 30, 2, 42, 213,147, 24, 84, 103,219, 52, @@ -237,13 +272,12 @@ const byte GMT_drywet_gp[] PROGMEM = { 212, 1, 1,111, 255, 1, 7, 33}; - // Gradient palette "ib15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte ib15_gp[] PROGMEM = { +DEFINE_PALETTE( ib15_gp ) { 0, 113, 91,147, 72, 157, 88, 78, 89, 208, 85, 33, @@ -251,26 +285,35 @@ const byte ib15_gp[] PROGMEM = { 141, 137, 31, 39, 255, 59, 33, 89}; - -// Gradient palette "Tertiary_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/vermillion/tn/Tertiary_01.png.index.html +// Gradient palette "Fuschia_7_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/fuschia/tn/Fuschia-7.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Tertiary_01_gp[] PROGMEM = { - 0, 0, 1,255, - 63, 3, 68, 45, - 127, 23,255, 0, - 191, 100, 68, 1, - 255, 255, 1, 4}; +DEFINE_PALETTE( Fuschia_7_gp ) { + 0, 43, 3,153, + 63, 100, 4,103, + 127, 188, 5, 66, + 191, 161, 11,115, + 255, 135, 20,182}; + +// Gradient palette "es_emerald_dragon_08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 bytes of program space. +DEFINE_PALETTE( es_emerald_dragon_08_gp ) { + 0, 97,255, 1, + 101, 47,133, 1, + 178, 13, 43, 1, + 255, 2, 10, 1}; // Gradient palette "lava_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 52 bytes of program space. -const byte lava_gp[] PROGMEM = { +DEFINE_PALETTE( lava_gp ) { 0, 0, 0, 0, 46, 18, 0, 0, 96, 113, 0, 0, @@ -285,28 +328,26 @@ const byte lava_gp[] PROGMEM = { 244, 255,255, 71, 255, 255,255,255}; - -// Gradient palette "fierce_ice_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fierce-ice.png.index.html +// Gradient palette "fire_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fire.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte fierce_ice_gp[] PROGMEM = { - 0, 0, 0, 0, - 59, 0, 9, 45, - 119, 0, 38,255, - 149, 3,100,255, - 180, 23,199,255, - 217, 100,235,255, +DEFINE_PALETTE( fire_gp ) { + 0, 1, 1, 0, + 76, 32, 5, 0, + 146, 192, 24, 0, + 197, 220,105, 5, + 240, 252,255, 31, + 250, 252,255,111, 255, 255,255,255}; - // Gradient palette "Colorfull_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 44 bytes of program space. -const byte Colorfull_gp[] PROGMEM = { +DEFINE_PALETTE( Colorfull_gp ) { 0, 10, 85, 5, 25, 29,109, 18, 60, 59,138, 42, @@ -319,13 +360,26 @@ const byte Colorfull_gp[] PROGMEM = { 168, 100,180,155, 255, 22,121,174}; +// Gradient palette "Magenta_Evening_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Magenta_Evening.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( Magenta_Evening_gp ) { + 0, 71, 27, 39, + 31, 130, 11, 51, + 63, 213, 2, 64, + 70, 232, 1, 66, + 76, 252, 1, 69, + 108, 123, 2, 51, + 255, 46, 9, 35}; // Gradient palette "Pink_Purple_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 44 bytes of program space. -const byte Pink_Purple_gp[] PROGMEM = { +DEFINE_PALETTE( Pink_Purple_gp ) { 0, 19, 2, 39, 25, 26, 4, 45, 51, 33, 6, 52, @@ -338,13 +392,12 @@ const byte Pink_Purple_gp[] PROGMEM = { 183, 128, 57,155, 255, 146, 40,123}; - // Gradient palette "Sunset_Real_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte Sunset_Real_gp[] PROGMEM = { +DEFINE_PALETTE( Sunset_Real_gp ) { 0, 120, 0, 0, 22, 179, 22, 0, 51, 255,104, 0, @@ -353,74 +406,12 @@ const byte Sunset_Real_gp[] PROGMEM = { 198, 16, 0,130, 255, 0, 0,160}; - -// Gradient palette "Sunset_Yellow_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 44 bytes of program space. - -const byte Sunset_Yellow_gp[] PROGMEM = { - 0, 10, 62,123, - 36, 56,130,103, - 87, 153,225, 85, - 100, 199,217, 68, - 107, 255,207, 54, - 115, 247,152, 57, - 120, 239,107, 61, - 128, 247,152, 57, - 180, 255,207, 54, - 223, 255,227, 48, - 255, 255,248, 42}; - - -// Gradient palette "Beech_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Beech.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 60 bytes of program space. - -const byte Beech_gp[] PROGMEM = { - 0, 255,252,214, - 12, 255,252,214, - 22, 255,252,214, - 26, 190,191,115, - 28, 137,141, 52, - 28, 112,255,205, - 50, 51,246,214, - 71, 17,235,226, - 93, 2,193,199, - 120, 0,156,174, - 133, 1,101,115, - 136, 1, 59, 71, - 136, 7,131,170, - 208, 1, 90,151, - 255, 0, 56,133}; - - -// Gradient palette "Another_Sunset_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Another_Sunset.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte Another_Sunset_gp[] PROGMEM = { - 0, 110, 49, 11, - 29, 55, 34, 10, - 68, 22, 22, 9, - 68, 239,124, 8, - 97, 220,156, 27, - 124, 203,193, 61, - 178, 33, 53, 56, - 255, 0, 1, 52}; - - - - - // Gradient palette "es_autumn_19_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 52 bytes of program space. -const byte es_autumn_19_gp[] PROGMEM = { +DEFINE_PALETTE( es_autumn_19_gp ) { 0, 26, 1, 1, 51, 67, 4, 1, 84, 118, 14, 1, @@ -435,13 +426,12 @@ const byte es_autumn_19_gp[] PROGMEM = { 249, 17, 1, 1, 255, 17, 1, 1}; - // Gradient palette "BlacK_Blue_Magenta_White_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte BlacK_Blue_Magenta_White_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Blue_Magenta_White_gp ) { 0, 0, 0, 0, 42, 0, 0, 45, 84, 0, 0,255, @@ -450,26 +440,24 @@ const byte BlacK_Blue_Magenta_White_gp[] PROGMEM = { 212, 255, 55,255, 255, 255,255,255}; - // Gradient palette "BlacK_Magenta_Red_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte BlacK_Magenta_Red_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Magenta_Red_gp ) { 0, 0, 0, 0, 63, 42, 0, 45, 127, 255, 0,255, 191, 255, 0, 45, 255, 255, 0, 0}; - // Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Red_Magenta_Yellow_gp ) { 0, 0, 0, 0, 42, 42, 0, 0, 84, 255, 0, 0, @@ -478,377 +466,1632 @@ const byte BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { 212, 255, 55, 45, 255, 255,255, 0}; - // Gradient palette "Blue_Cyan_Yellow_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Blue_Cyan_Yellow_gp[] PROGMEM = { +DEFINE_PALETTE( Blue_Cyan_Yellow_gp ) { 0, 0, 0,255, 63, 0, 55,255, 127, 0,255,255, 191, 42,255, 45, 255, 255,255, 0}; +// Gradient palette "Sunset_Yellow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( Sunset_Yellow_gp ) { + 0, 10, 62,123, + 36, 56,130,103, + 87, 153,225, 85, + 100, 199,217, 68, + 107, 255,207, 54, + 115, 247,152, 57, + 120, 239,107, 61, + 128, 247,152, 57, + 180, 255,207, 54, + 223, 255,227, 48, + 255, 255,248, 42}; + -//Custom palette by Aircoookie - -const byte Orange_Teal_gp[] PROGMEM = { - 0, 0,150, 92, - 55, 0,150, 92, - 200, 255, 72, 0, - 255, 255, 72, 0}; - -//Custom palette by Aircoookie - -const byte Tiamat_gp[] PROGMEM = { - 0, 1, 2, 14, //gc - 33, 2, 5, 35, //gc from 47, 61,126 - 100, 13,135, 92, //gc from 88,242,247 - 120, 43,255,193, //gc from 135,255,253 - 140, 247, 7,249, //gc from 252, 69,253 - 160, 193, 17,208, //gc from 231, 96,237 - 180, 39,255,154, //gc from 130, 77,213 - 200, 4,213,236, //gc from 57,122,248 - 220, 39,252,135, //gc from 177,254,255 - 240, 193,213,253, //gc from 203,239,253 - 255, 255,249,255}; - -//Custom palette by Aircoookie - -const byte April_Night_gp[] PROGMEM = { - 0, 1, 5, 45, //deep blue - 10, 1, 5, 45, - 25, 5,169,175, //light blue - 40, 1, 5, 45, - 61, 1, 5, 45, - 76, 45,175, 31, //green - 91, 1, 5, 45, - 112, 1, 5, 45, - 127, 249,150, 5, //yellow - 143, 1, 5, 45, - 162, 1, 5, 45, - 178, 255, 92, 0, //pastel orange - 193, 1, 5, 45, - 214, 1, 5, 45, - 229, 223, 45, 72, //pink - 244, 1, 5, 45, - 255, 1, 5, 45}; - -const byte Orangery_gp[] PROGMEM = { - 0, 255, 95, 23, - 30, 255, 82, 0, - 60, 223, 13, 8, - 90, 144, 44, 2, - 120, 255,110, 17, - 150, 255, 69, 0, - 180, 158, 13, 11, - 210, 241, 82, 17, - 255, 213, 37, 4}; - -//inspired by Mark Kriegsman https://gist.github.com/kriegsman/756ea6dcae8e30845b5a -const byte C9_gp[] PROGMEM = { - 0, 184, 4, 0, //red - 60, 184, 4, 0, - 65, 144, 44, 2, //amber - 125, 144, 44, 2, - 130, 4, 96, 2, //green - 190, 4, 96, 2, - 195, 7, 7, 88, //blue - 255, 7, 7, 88}; - -const byte Sakura_gp[] PROGMEM = { - 0, 196, 19, 10, - 65, 255, 69, 45, - 130, 223, 45, 72, - 195, 255, 82,103, - 255, 223, 13, 17}; - -const byte Aurora_gp[] PROGMEM = { - 0, 1, 5, 45, //deep blue - 64, 0,200, 23, - 128, 0,255, 0, //green - 170, 0,243, 45, - 200, 0,135, 7, - 255, 1, 5, 45};//deep blue - -const byte Atlantica_gp[] PROGMEM = { - 0, 0, 28,112, //#001C70 - 50, 32, 96,255, //#2060FF - 100, 0,243, 45, - 150, 12, 95, 82, //#0C5F52 - 200, 25,190, 95, //#19BE5F - 255, 40,170, 80};//#28AA50 - - const byte C9_2_gp[] PROGMEM = { - 0, 6, 126, 2, //green - 45, 6, 126, 2, - 45, 4, 30, 114, //blue - 90, 4, 30, 114, - 90, 255, 5, 0, //red - 135, 255, 5, 0, - 135, 196, 57, 2, //amber - 180, 196, 57, 2, - 180, 137, 85, 2, //yellow - 255, 137, 85, 2}; - - //C9, but brighter and with a less purple blue - const byte C9_new_gp[] PROGMEM = { - 0, 255, 5, 0, //red - 60, 255, 5, 0, - 60, 196, 57, 2, //amber (start 61?) - 120, 196, 57, 2, - 120, 6, 126, 2, //green (start 126?) - 180, 6, 126, 2, - 180, 4, 30, 114, //blue (start 191?) - 255, 4, 30, 114}; - -// Gradient palette "temperature_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/arendal/tn/temperature.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 144 bytes of program space. - -const byte temperature_gp[] PROGMEM = { - 0, 1, 27,105, - 14, 1, 40,127, - 28, 1, 70,168, - 42, 1, 92,197, - 56, 1,119,221, - 70, 3,130,151, - 84, 23,156,149, - 99, 67,182,112, - 113, 121,201, 52, - 127, 142,203, 11, - 141, 224,223, 1, - 155, 252,187, 2, - 170, 247,147, 1, - 184, 237, 87, 1, - 198, 229, 43, 1, - 226, 171, 2, 2, - 240, 80, 3, 3, - 255, 80, 3, 3}; - - const byte Aurora2_gp[] PROGMEM = { - 0, 17, 177, 13, //Greenish - 64, 121, 242, 5, //Greenish - 128, 25, 173, 121, //Turquoise - 192, 250, 77, 127, //Pink - 255, 171, 101, 221 //Purple - }; - - // Gradient palette "bhw1_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_01.png.index.html +// Gradient palette "cloud_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/cloud.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 12 bytes of program space. -const byte retro_clown_gp[] PROGMEM = { - 0, 227,101, 3, - 117, 194, 18, 19, - 255, 92, 8,192}; +DEFINE_PALETTE( cloud_gp ) { + 0, 247,149, 91, + 127, 208, 32, 71, + 255, 42, 79,188}; -// Gradient palette "bhw1_04_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_04.png.index.html + +// Gradient palette "fireandice_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/fireandice.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. +// Size: 24 bytes of program space. -const byte candy_gp[] PROGMEM = { - 0, 229,227, 1, - 15, 227,101, 3, - 142, 40, 1, 80, - 198, 17, 1, 79, - 255, 0, 0, 45}; +DEFINE_PALETTE( fireandice_gp ) { + 0, 80, 2, 1, + 51, 206, 15, 1, + 101, 242, 34, 1, + 153, 16, 67,128, + 204, 2, 21, 69, + 255, 1, 2, 4}; -// Gradient palette "bhw1_05_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_05.png.index.html +// Gradient palette "bhw2_39_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_39.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 8 bytes of program space. +// Size: 28 bytes of program space. + +DEFINE_PALETTE( bhw2_39_gp ) { + 0, 2,184,188, + 33, 56, 27,162, + 66, 56, 27,162, + 122, 255,255, 45, + 150, 227, 65, 6, + 201, 67, 13, 27, + 255, 16, 1, 53}; + +// Gradient palette "tashangel_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/tashangel.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. -const byte toxy_reaf_gp[] PROGMEM = { - 0, 1,221, 53, - 255, 73, 3,178}; +DEFINE_PALETTE( tashangel_gp ) { + 0, 133, 68,197, + 51, 2, 1, 33, + 101, 50, 35,130, + 153, 199,225,237, + 204, 41,187,228, + 255, 133, 68,197}; -// Gradient palette "bhw1_06_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_06.png.index.html +// Gradient palette "butterflytalker_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/butterflytalker.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. +// Size: 28 bytes of program space. -const byte fairy_reaf_gp[] PROGMEM = { - 0, 184, 1,128, - 160, 1,193,182, - 219, 153,227,190, - 255, 255,255,255}; +DEFINE_PALETTE( butterflytalker_gp ) { + 0, 1, 1, 6, + 51, 6, 11, 52, + 89, 107,107,192, + 127, 101,161,192, + 165, 107,107,192, + 204, 6, 11, 52, + 255, 0, 0, 0}; -// Gradient palette "bhw1_14_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_14.png.index.html +// Gradient palette "os250k_metres_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/os/tn/os250k-metres.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( os250k_metres_gp ) { + 0, 255,255,255, + 11, 255,255,255, + 11, 255,252,214, + 34, 255,252,214, + 34, 255,248,178, + 57, 255,248,178, + 57, 255,211,130, + 81, 255,211,130, + 81, 255,176, 89, + 115, 255,176, 89, + 115, 255,147, 63, + 173, 255,147, 63, + 173, 255,127, 55, + 255, 255,127, 55}; + +// Gradient palette "Night_Midnight_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Night_Midnight.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -const byte semi_blue_gp[] PROGMEM = { +DEFINE_PALETTE( Night_Midnight_gp ) { + 0, 15, 25, 27, + 36, 22, 48, 91, + 59, 32, 80,203, + 74, 110,154,228, + 77, 255,255,255, + 82, 110,154,228, + 96, 32, 80,203, + 189, 5, 18, 73, + 255, 0, 1, 12}; + +// Gradient palette "Afterdusk_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Afterdusk.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( Afterdusk_gp ) { 0, 0, 0, 0, - 12, 1, 1, 3, - 53, 8, 1, 22, - 80, 4, 6, 89, - 119, 2, 25,216, - 145, 7, 10, 99, - 186, 15, 2, 31, - 233, 2, 1, 5, - 255, 0, 0, 0}; + 25, 1, 1, 1, + 48, 1, 1, 1, + 67, 41, 49, 52, + 70, 210,219,216, + 73, 155,115,137, + 81, 109, 46, 78, + 86, 109, 46, 78, + 97, 109, 46, 78, + 165, 50, 15, 79, + 255, 16, 1, 80}; + +// Gradient palette "BlueSky_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/BlueSky.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. -// Gradient palette "bhw1_three_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_three.png.index.html +DEFINE_PALETTE( BlueSky_gp ) { + 0, 1, 7, 39, + 25, 2, 25, 88, + 61, 9, 53,160, + 88, 46,115,201, + 102, 120,203,245, + 108, 88,169,230, + 124, 63,139,216, + 216, 21, 96,203, + 255, 2, 60,188}; + +// Gradient palette "Gold_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Gold_Orange.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. +// Size: 52 bytes of program space. -const byte pink_candy_gp[] PROGMEM = { - 0, 255,255,255, - 45, 7, 12,255, - 112, 227, 1,127, - 112, 227, 1,127, - 140, 255,255,255, - 155, 227, 1,127, - 196, 45, 1, 99, - 255, 255,255,255}; +DEFINE_PALETTE( Gold_Orange_gp ) { + 0, 244, 88, 11, + 21, 247,118, 26, + 40, 249,152, 50, + 62, 252,201, 82, + 72, 255,255,125, + 79, 255,211,119, + 83, 255,169,112, + 87, 255,211,119, + 94, 255,255,125, + 103, 244,207, 54, + 118, 237,164, 16, + 202, 242,124, 13, + 255, 244, 88, 11}; + +// Gradient palette "Analogous_02_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/vermillion/tn/Analogous_02.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Analogous_02_gp ) { + 0, 32, 0,123, + 63, 110, 5, 79, + 127, 255, 23, 45, + 191, 255, 21, 30, + 255, 255, 18, 18}; -// Gradient palette "bhw1_w00t_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_w00t.png.index.html +// Gradient palette "Analogous_04a_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/pink/tn/Analogous_04a.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. +// Size: 36 bytes of program space. -const byte red_reaf_gp[] PROGMEM = { - 0, 3, 13, 43, - 104, 78,141,240, - 188, 255, 0, 0, - 255, 28, 1, 1}; +DEFINE_PALETTE( Analogous_04a_gp ) { + 0, 67, 55,255, + 42, 67, 55,255, + 84, 67, 55,255, + 84, 120, 33,255, + 127, 120, 33,255, + 170, 120, 33,255, + 170, 255, 23, 45, + 212, 255, 23, 45, + 255, 255, 23, 45}; + +// Gradient palette "Cyan_Orange_Stripped_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Cyan_Orange_Stripped.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 48 bytes of program space. + +DEFINE_PALETTE( Cyan_Orange_Stripped_gp ) { + 0, 1,108,212, + 60, 1,108,212, + 121, 1,108,212, + 121, 0, 0, 0, + 124, 0, 0, 0, + 127, 0, 0, 0, + 127, 229,127, 15, + 188, 242,186, 92, + 248, 255,255,255, + 248, 0, 0, 0, + 251, 0, 0, 0, + 255, 0, 0, 0}; +// Gradient palette "Cyan_White_Green_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Cyan_White_Green.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Cyan_White_Green_gp ) { + 0, 0,255,255, + 63, 42,255,255, + 127, 255,255,255, + 191, 42,255, 45, + 255, 0,255, 0}; -// Gradient palette "bhw2_23_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_23.png.index.html +// Gradient palette "Wild_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/mp/tn/Wild_Orange.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Red & Flash in SR -// Size: 28 bytes of program space. +// Size: 56 bytes of program space. -const byte aqua_flash_gp[] PROGMEM = { +DEFINE_PALETTE( Wild_Orange_gp ) { 0, 0, 0, 0, - 66, 57,227,233, - 96, 255,255, 8, - 124, 255,255,255, - 153, 255,255, 8, - 188, 57,227,233, - 255, 0, 0, 0}; + 0, 144, 11, 1, + 0, 144, 11, 1, + 5, 144, 11, 1, + 10, 194, 36, 1, + 30, 252, 79, 1, + 86, 249,175,100, + 106, 244,122, 25, + 124, 237, 79, 1, + 157, 244,154, 2, + 196, 252,255, 5, + 209, 252,223, 3, + 239, 255,108, 1, + 255, 255, 36, 1}; + +// Gradient palette "IKat_Radial_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/mp/tn/IKat_Radial.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. -// Gradient palette "bhw2_xc_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_xc.png.index.html +DEFINE_PALETTE( IKat_Radial_gp ) { + 0, 3, 7, 4, + 56, 255,255,255, + 127, 3, 7, 4, + 196, 255,255,255, + 255, 3, 7, 4}; + +// Gradient palette "Citrus_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Citrus.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// YBlue in SR -// Size: 28 bytes of program space. +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Citrus_gp ) { + 0, 252,164, 5, + 63, 149,25, 3, + 135, 255,166, 9, + 201, 147,39, 3, + 255, 237,119, 4}; + +// Gradient palette "Teal_Blue_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Teal_Blue.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Teal_Blue_gp ) { + 0, 1, 73, 88, + 63, 1, 43, 52, + 127, 1, 77, 95, + 196, 1, 58, 67, + 255, 1, 45, 50}; + +// Gradient palette "Ldby_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Ldby_Orange.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Ldby_Orange_gp ) { + 0, 217, 45, 17, + 61, 179, 21, 8, + 130, 222, 49, 21, + 193, 203, 32, 7, + 255, 173, 22, 6}; + +// Gradient palette "purple_orange_d07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/purple-orange-d07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( purple_orange_d07_gp ) { + 0, 53, 27, 91, + 36, 53, 27, 91, + 36, 121, 55,111, + 72, 121, 55,111, + 72, 179,107,137, + 109, 179,107,137, + 109, 179,189,182, + 145, 179,189,182, + 145, 234,152, 59, + 182, 234,152, 59, + 182, 227, 92, 11, + 218, 227, 92, 11, + 218, 165, 40, 1, + 255, 165, 40, 1}; + +// Gradient palette "blue_tan_d08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/blue-tan-d08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( blue_tan_d08_gp ) { + 0, 7, 77,210, + 31, 7, 77,210, + 31, 21,112,216, + 63, 21,112,216, + 63, 53,149,207, + 95, 53,149,207, + 95, 123,180,192, + 127, 123,180,192, + 127, 186,186,127, + 159, 186,186,127, + 159, 182,159, 50, + 191, 182,159, 50, + 191, 155,117, 14, + 223, 155,117, 14, + 223, 115, 72, 2, + 255, 115, 72, 2}; + +// Gradient palette "green_purple_d07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/green-purple-d07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( green_purple_d07_gp ) { + 0, 1, 90, 12, + 36, 1, 90, 12, + 36, 12,147, 51, + 72, 12,147, 51, + 72, 56,189,120, + 109, 56,189,120, + 109, 179,189,182, + 145, 179,189,182, + 145, 179,107,137, + 182, 179,107,137, + 182, 121, 55,111, + 218, 121, 55,111, + 218, 53, 27, 91, + 255, 53, 27, 91}; + +// Gradient palette "knoza_00_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/knoza/tn/knoza-00.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( knoza_00_gp ) { + 0, 56, 56,237, + 1, 115, 1, 1, + 24, 115, 1, 1, + 25, 237,130, 46, + 101, 237,186, 1, + 113, 237,186, 1, + 115, 2, 1, 1, + 138, 2, 1, 1, + 139, 237,186, 1, + 153, 237,186, 1, + 228, 237,130, 46, + 229, 115, 1, 1, + 253, 115, 1, 1, + 255, 56, 56,237}; +// Gradient palette "knoza_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/knoza/tn/knoza-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( knoza_18_gp ) { + 0, 8, 1, 1, + 2, 1,239, 1, + 51, 1,239, 1, + 52, 175,130, 1, + 100, 175,130, 1, + 101, 1, 1, 1, + 115, 1, 1, 1, + 117, 237,239,237, + 138, 237,239,237, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 175,130, 1, + 203, 175,130, 1, + 203, 1,239, 1, + 252, 1,239, 1, + 255, 8, 1, 1}; + +// Gradient palette "calpan_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/calpan/tn/calpan-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 40 bytes of program space. + +DEFINE_PALETTE( calpan_18_gp ) { + 0, 133, 31,137, + 1, 117, 2, 88, + 24, 117, 2, 88, + 25, 239,241,245, + 32, 239,241,245, + 51, 239,241,245, + 53, 117, 2, 88, + 76, 117, 2, 88, + 77, 133, 31,137, + 255, 239,241,245}; + +// Gradient palette "calbayo_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/calbayo/tn/calbayo-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( calbayo_18_gp ) { + 0, 210,131, 1, + 60, 210,131, 1, + 62, 41, 2, 3, + 99, 41, 2, 3, + 100, 106, 40, 1, + 101, 210,131, 1, + 126, 210,131, 1, + 127, 210, 31, 6, + 165, 210, 31, 6, + 166, 210,131, 1, + 188, 210,131, 1, + 191, 3, 6, 6, + 226, 3, 6, 6, + 228, 210,131, 1, + 253, 210,131, 1, + 255, 1, 58, 29}; + +// Gradient palette "fib53_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( fib53_15_gp ) { + 0, 239, 11, 31, + 101, 239, 11, 31, + 101, 239,241,240, + 127, 239,241,240, + 128, 1, 1, 1, + 152, 1, 1, 1, + 153, 239,241,240, + 178, 239,241,240, + 179, 239, 11, 31, + 202, 239, 11, 31, + 203, 239,241,240, + 229, 239,241,240, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "grindylow_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( grindylow_15_gp ) { + 0, 101,241,105, + 127, 26,182,105, + 255, 26,151, 80}; + +// Gradient palette "grindylow_21_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-21.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( grindylow_21_gp ) { + 0, 101,241,105, + 51, 60,241,240, + 128, 26,241,240, + 191, 26,182,105, + 255, 26,151, 80}; + +// Gradient palette "konjo_08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( konjo_08_gp ) { + 0, 213,229,240, + 127, 213,229,240, + 128, 133,168,188, + 150, 133,168,188, + 150, 21, 57, 91, + 152, 0, 6, 33, + 177, 0, 6, 33, + 179, 0, 2, 9, + 200, 0, 2, 9, + 203, 0, 6, 33, + 227, 0, 6, 33, + 229, 30, 0, 2, + 252, 30, 0, 2, + 255, 0, 6, 33}; + +// Gradient palette "konjo_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 88 bytes of program space. + +DEFINE_PALETTE( konjo_18_gp ) { + 0, 109, 5, 1, + 13, 109, 5, 1, + 14, 133,168,188, + 37, 133,168,188, + 39, 0, 6, 33, + 63, 0, 6, 33, + 63, 77,130,184, + 87, 77,130,184, + 89, 0, 2, 9, + 114, 0, 2, 9, + 115, 213,229,240, + 140, 213,229,240, + 142, 0, 2, 9, + 165, 0, 2, 9, + 166, 77,130,184, + 191, 77,130,184, + 193, 0, 6, 33, + 216, 0, 6, 33, + 217, 133,168,188, + 240, 133,168,188, + 241, 109, 5, 1, + 255, 109, 5, 1}; + +// Gradient palette "konjo_19_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-19.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 88 bytes of program space. + +DEFINE_PALETTE( konjo_19_gp ) { + 0, 109, 5, 1, + 13, 109, 5, 1, + 14, 133,168,188, + 37, 133,168,188, + 39, 0, 6, 33, + 63, 0, 6, 33, + 65, 109, 5, 1, + 87, 109, 5, 1, + 89, 0, 2, 9, + 114, 0, 2, 9, + 115, 213,229,240, + 140, 213,229,240, + 142, 0, 2, 9, + 165, 0, 2, 9, + 166, 109, 5, 1, + 192, 109, 5, 1, + 193, 0, 6, 33, + 216, 0, 6, 33, + 217, 133,168,188, + 240, 133,168,188, + 241, 109, 5, 1, + 255, 109, 5, 1}; + +// Gradient palette "konkikyo_19_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konkikyo/tn/konkikyo-19.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( konkikyo_19_gp ) { + 0, 1, 2, 9, + 101, 1, 2, 9, + 102, 199,213,252, + 122, 199,213,252, + 126, 1, 2, 9, + 151, 1, 2, 9, + 151, 24,128,245, + 177, 24,128,245, + 178, 1, 2, 9, + 203, 1, 2, 9, + 203, 177,133, 1, + 229, 177,133, 1, + 229, 1, 2, 9, + 252, 1, 2, 9, + 255, 1, 2, 9}; + +// Gradient palette "sulz_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_22_gp ) { + 0, 247, 54, 7, + 1, 247, 54, 7, + 24, 247, 54, 7, + 25, 1, 1, 1, + 51, 1, 1, 1, + 51, 247,248,247, + 75, 247,248,247, + 75, 247, 54, 7, + 101, 247, 54, 7, + 102, 1, 1, 1, + 115, 1, 1, 1, + 115, 247, 54, 7, + 138, 247, 54, 7, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 247, 54, 7, + 179, 247, 54, 7, + 181, 247,248,247, + 202, 247,248,247, + 203, 1, 1, 1, + 228, 1, 1, 1, + 228, 247, 54, 7, + 249, 247, 54, 7, + 255, 247, 54, 7}; + +// Gradient palette "Pills_2_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/icons/tn/Pills-2.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( Pills_2_gp ) { + 0, 192,147, 11, + 127, 148,104, 59, + 255, 109, 69,155}; + +// Gradient palette "Pink_Yellow_Orange_1_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/icons/tn/Pink-Yellow-Orange-1.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Pink_Yellow_Orange_1_gp ) { + 0, 255,199, 0, + 34, 255,121, 0, + 106, 255, 63, 0, + 168, 194, 13, 6, + 255, 146, 1, 37}; +// Gradient palette "es_autumn_04_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_04.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_autumn_04_gp ) { + 0, 2, 1, 1, + 101, 27, 1, 0, + 165, 210, 22, 1, + 234, 255,166, 42, + 255, 255,166, 42}; + +// Gradient palette "es_autumn_02_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_02.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. -const byte yelblu_hot_gp[] PROGMEM = { - 0, 4, 2, 9, - 58, 16, 0, 47, - 122, 24, 0, 16, - 158, 144, 9, 1, - 183, 179, 45, 1, - 219, 220,114, 2, - 255, 234,237, 1}; +DEFINE_PALETTE( es_autumn_02_gp ) { + 0, 86, 6, 1, + 127, 255,255,125, + 153, 255,255,125, + 242, 194, 96, 1, + 255, 194, 96, 1}; - // Gradient palette "bhw2_45_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_45.png.index.html +// Gradient palette "es_candide_30_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/candide/tn/es_candide_30.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte lite_light_gp[] PROGMEM = { - 0, 0, 0, 0, - 9, 1, 1, 1, - 40, 5, 5, 6, - 66, 5, 5, 6, - 101, 10, 1, 12, - 255, 0, 0, 0}; +DEFINE_PALETTE( es_candide_30_gp ) { + 0, 242,244,242, + 63, 133,255,137, + 127, 242,146,194, + 191, 104,187,245, + 252, 232,239,237, + 255, 232,239,237}; + +// Gradient palette "es_chic_16_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/chic/tn/es_chic_16.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. -// Gradient palette "bhw2_22_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_22.png.index.html +DEFINE_PALETTE( es_chic_16_gp ) { + 0, 4, 1, 1, + 51, 135, 99, 3, + 63, 222,248,160, + 76, 110,118, 50, + 89, 72, 55, 6, + 127, 4, 1, 1, + 165, 72, 55, 6, + 172, 90, 84, 22, + 178, 110,118, 50, + 191, 222,248,160, + 204, 135, 99, 3, + 247, 4, 1, 1, + 255, 4, 1, 1}; + +// Gradient palette "es_coffee_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/coffee/tn/es_coffee_01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( es_coffee_01_gp ) { + 0, 152,173,123, + 13, 152,154,106, + 25, 150,136, 91, + 63, 133, 78, 35, + 86, 112, 46, 15, + 114, 86, 15, 1, + 153, 68, 6, 1, + 178, 46, 1, 1, + 191, 31, 1, 1, + 216, 14, 1, 0, + 255, 6, 1, 0}; + +// Gradient palette "es_emerald_dragon_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_01.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Pink Plasma in SR // Size: 20 bytes of program space. -const byte red_flash_gp[] PROGMEM = { - 0, 0, 0, 0, - 99, 227, 1, 1, - 130, 249,199, 95, - 155, 227, 1, 1, - 255, 0, 0, 0}; +DEFINE_PALETTE( es_emerald_dragon_01_gp ) { + 0, 1, 1, 1, + 79, 1, 19, 7, + 130, 1, 59, 25, + 229, 28,255,255, + 255, 28,255,255}; + +// Gradient palette "es_landscape_57_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_57.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_57_gp ) { + 0, 27, 91, 0, + 89, 126,171,106, + 91, 157,199,255, + 143, 45,142,245, + 191, 3, 96,235, + 255, 1, 15, 22}; +// Gradient palette "es_landscape_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_22_gp ) { + 0, 1, 6, 1, + 38, 7, 49, 1, + 63, 21,124, 1, + 68, 173,244,252, + 127, 10,164,156, + 255, 5, 68, 66}; +// Gradient palette "es_landscape_47_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_47.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_47_gp ) { + 0, 175,125, 44, + 38, 88, 45, 3, + 58, 46, 27, 1, + 76, 20, 14, 0, + 79, 249,193,140, + 255, 121, 27, 1}; + +// Gradient palette "es_landscape_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( es_landscape_10_gp ) { + 0, 244,213, 55, + 24, 242,209, 53, + 51, 237,203, 51, + 63, 210,252,252, + 89, 171,225,230, + 127, 123,221,203, + 204, 25,122,144, + 255, 10, 93,115}; + +// Gradient palette "es_landscape_76_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_76.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_landscape_76_gp ) { + 0, 252,178, 82, + 127, 208, 91, 7, + 132, 153,173,188, + 191, 163,187,221, + 255, 130,191,250}; + +// Gradient palette "es_landscape_61_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_61.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_landscape_61_gp ) { + 0, 90,199, 1, + 89, 73,219, 6, + 127, 34,189, 6, + 128, 113,221, 75, + 130, 255,252,255, + 178, 64,189,255, + 255, 1,122,255}; + +// Gradient palette "es_landscape_60_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_60.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( es_landscape_60_gp ) { + 0, 161,112, 18, + 51, 130, 78, 1, + 89, 95, 59, 1, + 91, 133,151,140, + 136, 22, 92, 91, + 178, 1, 49, 52, + 242, 0, 1, 1, + 255, 0, 1, 1}; + +// Gradient palette "es_landscape_51_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_51.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_51_gp ) { + 0, 128,128,103, + 39, 165,161,144, + 76, 206,195,190, + 114, 15, 71,247, + 178, 1, 9, 71, + 255, 1, 1, 10}; + +// Gradient palette "es_landscape_06_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_06.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( es_landscape_06_gp ) { + 0, 90,199, 1, + 89, 173,244,252, + 255, 57,175,207}; + +// Gradient palette "es_ocean_breeze_049_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_049.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. -// Gradient palette "bhw3_40_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_40.png.index.html +DEFINE_PALETTE( es_ocean_breeze_049_gp ) { + 0, 184,231,250, + 76, 0,112,203, + 77, 29,168,228, + 79, 179,235,255, + 153, 64,189,255, + 255, 0,124,199}; + +// Gradient palette "es_ocean_breeze_057_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_057.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte blink_red_gp[] PROGMEM = { +DEFINE_PALETTE( es_ocean_breeze_057_gp ) { + 0, 115, 82, 49, + 76, 87, 51, 22, + 79, 249, 71, 9, + 101, 249,122, 17, + 140, 247,121, 38, + 178, 175,125, 71, + 229, 123,108, 83, + 255, 83, 97, 83}; + +// Gradient palette "es_ocean_breeze_074_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_074.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_074_gp ) { 0, 1, 1, 1, - 43, 4, 1, 11, - 76, 10, 1, 3, - 109, 161, 4, 29, - 127, 255, 86,123, - 165, 125, 16,160, - 204, 35, 13,223, - 255, 18, 2, 18}; - -// Gradient palette "bhw3_52_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_52.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Blue in SR + 101, 34, 23, 3, + 127, 53, 26, 2, + 130, 203, 65, 7, + 153, 78, 56, 8, + 191, 22, 37, 11, + 255, 1, 4, 1}; + +// Gradient palette "es_pinksplash_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. + +DEFINE_PALETTE( es_pinksplash_05_gp ) { + 0, 206, 1, 25, + 20, 192, 45, 82, + 38, 179,182,182, + 76, 206, 1, 25, + 127, 255,135,252, + 178, 206, 1, 25, + 216, 179,182,182, + 231, 192, 45, 82, + 255, 206, 1, 25}; + +// Gradient palette "es_pinksplash_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte red_shift_gp[] PROGMEM = { - 0, 31, 1, 27, - 45, 34, 1, 16, - 99, 137, 5, 9, - 132, 213,128, 10, - 175, 199, 22, 1, - 201, 199, 9, 6, - 255, 1, 0, 1}; +DEFINE_PALETTE( es_pinksplash_10_gp ) { + 0, 26, 17, 27, + 63, 184, 1, 37, + 76, 234,141,174, + 89, 148, 2, 35, + 127, 26, 17, 27, + 252, 90, 65, 89, + 255, 90, 65, 89}; + +// Gradient palette "es_vintage_56_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_56.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. -// Gradient palette "bhw4_097_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_097.png.index.html +DEFINE_PALETTE( es_vintage_56_gp ) { + 0, 220,225,221, + 51, 83, 79, 7, + 109, 25, 0, 1, + 119, 255,131, 19, + 127, 217,221,184, + 135, 255,131, 19, + 145, 25, 0, 1, + 204, 60, 46, 1, + 255, 220,225,221}; + +// Gradient palette "es_vintage_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_10.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Red in SR -// Size: 44 bytes of program space. +// Size: 16 bytes of program space. + +DEFINE_PALETTE( es_vintage_10_gp ) { + 0, 1, 3, 1, + 51, 7, 1, 1, + 127, 112, 18, 0, + 255, 206,207,182}; -const byte red_tide_gp[] PROGMEM = { - 0, 247, 5, 0, - 28, 255, 67, 1, - 43, 234, 88, 11, - 58, 234,176, 51, - 84, 229, 28, 1, - 114, 113, 12, 1, - 140, 255,225, 44, - 168, 113, 12, 1, - 196, 244,209, 88, - 216, 255, 28, 1, - 255, 53, 1, 1}; - -// Gradient palette "bhw4_017_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_017.png.index.html +// Gradient palette "gold_yellow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/clth/tn/gold-yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( gold_yellow_gp ) { + 0, 0, 0, 0, + 94, 42, 29, 0, + 189, 255,135, 0, + 213, 255,189, 4, + 238, 255,255, 25, + 246, 255,255,103, + 255, 255,255,255}; + +// Gradient palette "radioactive_slime_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/faun/tn/radioactive-slime.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( radioactive_slime_gp ) { + 0, 0, 0, 0, + 25, 1, 4, 1, + 58, 1, 19, 1, + 76, 4, 30, 4, + 101, 17, 43, 13, + 118, 12, 69, 13, + 135, 8,100, 13, + 150, 27,146, 36, + 174, 59,199, 75, + 195, 135,195, 79, + 222, 255,189, 84, + 239, 255,221, 96, + 255, 255,255,111}; + +// Gradient palette "pastel_rainbow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/othr/tn/pastel-rainbow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( pastel_rainbow_gp ) { + 0, 0, 0, 0, + 33, 1, 2, 8, + 67, 7, 12, 45, + 88, 27, 18, 31, + 110, 67, 27, 19, + 129, 83, 38, 52, + 147, 100, 53,103, + 168, 90, 96, 93, + 189, 79,156, 83, + 206, 110,178,132, + 222, 148,203,197, + 238, 197,227,223, + 255, 255,255,255}; + +// Gradient palette "purple_sunset_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/othr/tn/purple-sunset.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( purple_sunset_gp ) { + 0, 0, 0, 0, + 31, 1, 1, 1, + 62, 1, 1, 7, + 62, 3, 2, 6, + 63, 6, 4, 5, + 88, 16, 8, 9, + 114, 31, 14, 15, + 131, 45, 22, 22, + 148, 61, 31, 31, + 152, 65, 39, 37, + 155, 69, 48, 45, + 192, 118, 86, 46, + 225, 184,135, 47, + 238, 197,161, 72, + 255, 213,187,103}; + +// Gradient palette "rainfall_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/jjg/misc/tn/rainfall.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( rainfall_gp ) { + 0, 192,118, 3, + 36, 192,118, 3, + 36, 222,118, 24, + 72, 222,118, 24, + 72, 224,209, 37, + 109, 224,209, 37, + 109, 58,159, 43, + 145, 58,159, 43, + 145, 7,133, 52, + 182, 7,133, 52, + 182, 4,118, 50, + 218, 4,118, 50, + 218, 1, 85, 8, + 255, 1, 85, 8}; + +// Gradient palette "sulz_12_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-12.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_12_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 17, 38, 14, + 45, 17, 38, 14, + 46, 1, 3, 10, + 69, 1, 3, 10, + 70, 17, 38, 14, + 91, 17, 38, 14, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 17, 38, 14, + 182, 17, 38, 14, + 183, 1, 3, 10, + 206, 1, 3, 10, + 206, 17, 38, 14, + 228, 17, 38, 14, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_10_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 117, 1,168, + 45, 117, 1,168, + 46, 1, 3, 10, + 69, 1, 3, 10, + 69, 117, 1,168, + 91, 117, 1,168, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 117, 1,168, + 182, 117, 1,168, + 183, 1, 3, 10, + 206, 1, 3, 10, + 206, 117, 1,168, + 229, 117, 1,168, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_15_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 57, 1, 1, + 45, 57, 1, 1, + 46, 1, 3, 10, + 69, 1, 3, 10, + 69, 57, 1, 1, + 90, 57, 1, 1, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 57, 1, 1, + 181, 57, 1, 1, + 183, 1, 3, 10, + 206, 1, 3, 10, + 207, 57, 1, 1, + 229, 57, 1, 1, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_21_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-21.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_21_gp ) { + 0, 247,248, 7, + 1, 247,248, 7, + 23, 247,248, 7, + 25, 1, 1, 1, + 51, 1, 1, 1, + 51, 247,248,247, + 75, 247,248,247, + 75, 247,248, 7, + 100, 247,248, 7, + 102, 1, 1, 1, + 115, 1, 1, 1, + 115, 247,248, 7, + 138, 247,248, 7, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 247,248, 7, + 179, 247,248, 7, + 181, 247,248,247, + 202, 247,248,247, + 203, 1, 1, 1, + 228, 1, 1, 1, + 229, 247,248, 7, + 249, 247,248, 7, + 255, 247,248, 7}; + +// Gradient palette "fib53_07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( fib53_07_gp ) { + 0, 98,114,102, + 2, 98,114,240, + 24, 98,114,240, + 25, 239,114,240, + 50, 239,114,240, + 50, 98,241,240, + 75, 98,241,240, + 76, 239,114,102, + 101, 239,114,102, + 102, 239,241,240, + 118, 239,241,240, + 120, 1, 1, 1, + 134, 1, 1, 1, + 135, 239,241,240, + 151, 239,241,240, + 153, 239,114,102, + 177, 239,114,102, + 179, 98,241,240, + 203, 98,241,240, + 204, 239,114,240, + 228, 239,114,240, + 229, 98,114,240, + 252, 98,114,240, + 255, 98,114,102}; + +// Gradient palette "fib53_13_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-13.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( fib53_13_gp ) { + 0, 6, 61,240, + 101, 6, 61,240, + 101, 239,241,240, + 127, 239,241,240, + 128, 1, 1, 1, + 152, 1, 1, 1, + 153, 239,241,240, + 178, 239,241,240, + 178, 6, 61,240, + 202, 6, 61,240, + 203, 239,241,240, + 229, 239,241,240, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "fib53_17_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-17.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( fib53_17_gp ) { + 0, 227,231,140, + 2, 1, 1, 1, + 12, 1, 1, 1, + 13, 73,184, 31, + 76, 73,184, 31, + 77, 1, 1, 1, + 89, 1, 1, 1, + 89, 1,121, 1, + 166, 1,121, 1, + 166, 1, 1, 1, + 179, 1, 1, 1, + 179, 1, 56, 1, + 241, 1, 56, 1, + 241, 1, 1, 1, + 252, 1, 1, 1, + 255, 227,231,140}; + +// Gradient palette "fib53_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 72 bytes of program space. + +DEFINE_PALETTE( fib53_05_gp ) { + 0, 239,241,240, + 23, 239,241,240, + 25, 239,114,102, + 51, 239,114,102, + 51, 98,114,240, + 75, 98,114,240, + 77, 239,114,240, + 99, 239,114,240, + 101, 98,114,102, + 125, 98,114,102, + 127, 98,241,240, + 152, 98,241,240, + 153, 1, 1, 1, + 178, 1, 1, 1, + 179, 98,241,240, + 204, 98,241,240, + 205, 239,241,240, + 255, 239,241,240}; + +// Gradient palette "fib53_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( fib53_18_gp ) { + 0, 73,184, 31, + 179, 73,184, 31, + 179, 1, 1, 1, + 192, 1, 1, 1, + 193, 1, 56, 1, + 205, 1, 56, 1, + 205, 239,241,240, + 216, 239,241,240, + 217, 1,121, 1, + 229, 1,121, 1, + 230, 1, 1, 1, + 243, 1, 1, 1, + 243, 227,231,140, + 255, 227,231,140}; + +// Gradient palette "fib53_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 80 bytes of program space. + +DEFINE_PALETTE( fib53_01_gp ) { + 0, 239,241,240, + 2, 6, 88, 77, + 30, 6, 88, 77, + 30, 239,241,240, + 45, 239,241,240, + 46, 73, 88, 77, + 61, 73, 88, 77, + 62, 239,241,240, + 77, 239,241,240, + 79, 6, 88, 77, + 173, 6, 88, 77, + 174, 239,241,240, + 191, 239,241,240, + 192, 73, 88, 77, + 209, 73, 88, 77, + 210, 239,241,240, + 224, 239,241,240, + 225, 6, 88, 77, + 252, 6, 88, 77, + 255, 239,241,240}; + +// Gradient palette "mccahon_16_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/mccahon/tn/mccahon-16.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( mccahon_16_gp ) { + 0, 237, 95, 29, + 61, 247,233,190, + 63, 109, 73, 1, + 125, 247,233,190, + 127, 186, 20, 5, + 190, 247,233,190, + 191, 3, 1, 1, + 255, 237, 95, 29}; + +// Gradient palette "frizzell_09_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-09.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( frizzell_09_gp ) { + 0, 242,142, 10, + 1, 137, 19, 0, + 63, 137, 19, 0, + 65, 210,189,119, + 76, 210,189,119, + 78, 79, 25, 1, + 88, 79, 25, 1, + 89, 210,189,119, + 102, 210,189,119, + 103, 1, 5, 6, + 115, 1, 5, 6, + 115, 45, 68, 64, + 137, 45, 68, 64, + 139, 1, 5, 6, + 152, 1, 5, 6, + 153, 210,189,119, + 163, 210,189,119, + 165, 79, 25, 1, + 175, 79, 25, 1, + 178, 210,189,119, + 188, 210,189,119, + 191, 137, 19, 0, + 252, 137, 19, 0, + 255, 242,142, 10}; + +// Gradient palette "frizzell_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( frizzell_10_gp ) { + 0, 45, 68, 64, + 11, 45, 68, 64, + 11, 242,142, 10, + 25, 242,142, 10, + 25, 1, 5, 6, + 38, 1, 5, 6, + 39, 210,189,119, + 49, 210,189,119, + 49, 79, 25, 1, + 63, 79, 25, 1, + 65, 210,189,119, + 76, 210,189,119, + 77, 137, 19, 0, + 255, 137, 19, 0}; + +// Gradient palette "frizzell_12_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-12.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 40 bytes of program space. -const byte candy2_gp[] PROGMEM = { - 0, 39, 33, 34, - 25, 4, 6, 15, - 48, 49, 29, 22, - 73, 224,173, 1, - 89, 177, 35, 5, - 130, 4, 6, 15, - 163, 255,114, 6, - 186, 224,173, 1, - 211, 39, 33, 34, - 255, 1, 1, 1}; +DEFINE_PALETTE( frizzell_12_gp ) { + 0, 45, 68, 64, + 2, 210,189,119, + 24, 210,189,119, + 25, 1, 5, 6, + 126, 1, 5, 6, + 126, 137, 19, 0, + 228, 137, 19, 0, + 230, 79, 25, 1, + 253, 79, 25, 1, + 255, 242,142, 10}; + +// Gradient palette "frizzell_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 80 bytes of program space. + +DEFINE_PALETTE( frizzell_05_gp ) { + 0, 222,133, 47, + 1, 27, 17, 4, + 28, 27, 17, 4, + 28, 247,146,178, + 53, 247,146,178, + 55, 5, 1, 1, + 84, 5, 1, 1, + 84, 247,146,178, + 112, 247,146,178, + 113, 5, 1, 1, + 139, 5, 1, 1, + 140, 247,146,178, + 166, 247,146,178, + 168, 5, 1, 1, + 195, 5, 1, 1, + 196, 247,146,178, + 223, 247,146,178, + 224, 27, 17, 4, + 253, 27, 17, 4, + 255, 222,133, 47}; + +// Gradient palette "haiyan_23_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/haiyan/tn/haiyan-23.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( haiyan_23_gp ) { + 0, 36,197,164, + 122, 36,197,164, + 124, 1, 1, 1, + 135, 1, 1, 1, + 136, 239,241,240, + 177, 239,241,240, + 178, 1, 1, 1, + 204, 1, 1, 1, + 205, 84,100, 88, + 229, 84,100, 88, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "janico_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/janico/tn/janico-22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 108 bytes of program space. + +DEFINE_PALETTE( janico_22_gp ) { + 0, 112,109, 87, + 2, 126,109,115, + 37, 126,109,115, + 38, 83, 99,115, + 76, 83, 99,115, + 77, 148,127,115, + 89, 148,127,115, + 90, 112, 65, 73, + 127, 112, 65, 73, + 128, 148,127,115, + 141, 148,127,115, + 141, 4, 1, 1, + 151, 4, 1, 1, + 152, 112, 65, 53, + 165, 112, 65, 53, + 166, 4, 1, 1, + 178, 4, 1, 1, + 179, 69, 65, 53, + 202, 69, 65, 53, + 203, 4, 1, 1, + 214, 4, 1, 1, + 216, 194,225,255, + 230, 194,225,255, + 231, 4, 1, 1, + 252, 4, 1, 1, + 253, 4, 1, 1, + 255, 194,225,255}; + // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly // by name every time. const byte* const gGradientPalettes[] PROGMEM = { + ib_jul01_gp, //13-00 Jul01 + es_vintage_57_gp, + es_vintage_01_gp, + es_rivendell_15_gp, + rgi_15_gp, + retro2_16_gp, + Analogous_1_gp, + es_pinksplash_08_gp, + es_pinksplash_07_gp, + Coral_reef_gp, + + es_ocean_breeze_068_gp, + es_ocean_breeze_036_gp, + departure_gp, + es_landscape_64_gp, + es_landscape_33_gp, + rainbowsherbet_gp, + gr65_hult_gp, + gr64_hult_gp, + GMT_drywet_gp, + ib15_gp, + + Fuschia_7_gp, + es_emerald_dragon_08_gp, + lava_gp, + fire_gp, + haiyan_23_gp, + Colorfull_gp, + Magenta_Evening_gp, + Pink_Purple_gp, + Sunset_Real_gp, + es_autumn_19_gp, + + BlacK_Blue_Magenta_White_gp, + BlacK_Magenta_Red_gp, + BlacK_Red_Magenta_Yellow_gp, + Blue_Cyan_Yellow_gp, + Sunset_Yellow_gp, + cloud_gp, + fireandice_gp, + bhw2_39_gp, + rainfall_gp, + tashangel_gp, + + butterflytalker_gp, + os250k_metres_gp, + Night_Midnight_gp, + Afterdusk_gp, + BlueSky_gp, + Gold_Orange_gp, + frizzell_05_gp, + frizzell_09_gp, + frizzell_10_gp, + frizzell_12_gp, + + fib53_01_gp, + fib53_18_gp, + fib53_07_gp, + fib53_13_gp, + fib53_17_gp, + fib53_05_gp, + Analogous_02_gp, + Analogous_04a_gp, + Cyan_Orange_Stripped_gp, + Cyan_White_Green_gp, + + Wild_Orange_gp, + IKat_Radial_gp, + Citrus_gp, + Teal_Blue_gp, + Ldby_Orange_gp, + purple_orange_d07_gp, + blue_tan_d08_gp, + green_purple_d07_gp, + knoza_00_gp, + knoza_18_gp, + + calpan_18_gp, + calbayo_18_gp, + fib53_15_gp, + grindylow_15_gp, + grindylow_21_gp, + konjo_08_gp, + konjo_18_gp, + konjo_19_gp, + konkikyo_19_gp, + mccahon_16_gp, + sulz_10_gp, + sulz_12_gp, + sulz_15_gp, + sulz_21_gp, + sulz_22_gp, + Pills_2_gp, + Pink_Yellow_Orange_1_gp, + es_autumn_04_gp, + es_autumn_02_gp, + es_candide_30_gp, + es_chic_16_gp, + es_coffee_01_gp, + es_emerald_dragon_01_gp, + es_landscape_57_gp, + es_landscape_22_gp, + es_landscape_47_gp, + es_landscape_10_gp, + es_landscape_76_gp, + es_landscape_61_gp, + es_landscape_60_gp, + es_landscape_51_gp, + es_landscape_06_gp, + es_ocean_breeze_049_gp, + es_ocean_breeze_057_gp, + es_ocean_breeze_074_gp, + es_pinksplash_05_gp, + es_pinksplash_10_gp, + es_vintage_56_gp, + es_vintage_10_gp, + gold_yellow_gp, + radioactive_slime_gp, + pastel_rainbow_gp, + purple_sunset_gp, + janico_22_gp + +/* Sunset_Real_gp, //13-00 Sunset es_rivendell_15_gp, //14-01 Rivendell es_ocean_breeze_036_gp, //15-02 Breeze @@ -907,6 +2150,10 @@ const byte* const gGradientPalettes[] PROGMEM = { red_shift_gp, //68-55 Red Shift red_tide_gp, //69-56 Red Tide candy2_gp //70-57 Candy2 +*/ }; +const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); +#define GRADIENT_PALETTE_COUNT 114 + #endif diff --git a/wled00/wled.h b/wled00/wled.h index 055d65d3ed..706d8eaa92 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -12,6 +12,10 @@ //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG +#define WLED_DISABLE_MQTT +#define WLED_DISABLE_LOXONE +#define WLED_DISABLE_ALEXA +#define WLED_DISABLE_INFRARED // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From f4d52914680f35b46fa1ba8074caa079f64c5735 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 5 Jul 2022 16:25:51 -0700 Subject: [PATCH 027/263] First version of externally-driven displays --- usermods/Tubes/Tubes.h | 66 +++++++++++ usermods/Tubes/beats.h | 72 ++++++++++++ usermods/Tubes/debug.h | 56 +++++++++ usermods/Tubes/options.h | 83 +++++++++++++ usermods/Tubes/pattern.h | 187 ++++++++++++++++++++++++++++++ usermods/Tubes/timer.h | 67 +++++++++++ usermods/Tubes/util.h | 16 +++ usermods/Tubes/virtual_strip.h | 206 +++++++++++++++++++++++++++++++++ wled00/FX.cpp | 20 +++- wled00/const.h | 1 + wled00/usermods_list.cpp | 9 ++ 11 files changed, 779 insertions(+), 4 deletions(-) create mode 100644 usermods/Tubes/Tubes.h create mode 100644 usermods/Tubes/beats.h create mode 100644 usermods/Tubes/debug.h create mode 100644 usermods/Tubes/options.h create mode 100644 usermods/Tubes/pattern.h create mode 100644 usermods/Tubes/timer.h create mode 100644 usermods/Tubes/util.h create mode 100644 usermods/Tubes/virtual_strip.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h new file mode 100644 index 0000000000..2243004f91 --- /dev/null +++ b/usermods/Tubes/Tubes.h @@ -0,0 +1,66 @@ +#pragma once + +#include "wled.h" + +#include "util.h" +#include "options.h" + +// #define MASTERCONTROL + +#define MASTER_PIN 6 +#define NUM_LEDS 64 + +#define USERADIO + +#include "FX.h" + +#include "beats.h" +#include "virtual_strip.h" + +// #include "controller.h" +// #include "radio.h" +// #include "debug.h" + + +class TubesUsermod : public Usermod { + private: + BeatController beats; + // Radio radio; + // PatternController controller(NUM_LEDS, &beats, &radio); + // DebugController debug(&controller); + + void randomize(long seed) { + for (int i = 0; i < seed % 16; i++) { + randomSeed(random(INT_MAX)); + } + random16_add_entropy( random(INT_MAX) ); + } + + public: + void setup() { + // Start timing + globalTimer.setup(); + beats.setup(); + // controller.setup(0); + // debug.setup(); + } + + void loop() + { + EVERY_N_MILLISECONDS(1000) { + randomize(random(INT_MAX)); + } + + beats.update(); // ~30us + // controller.update(); // radio: 0-3000us patterns: 0-3000us lcd: ~50000us + // debug.update(); // ~25us + + // Draw after everything else is done + // controller.led_strip->update(master != NULL); // ~25us + + CRGB *external_buffer = WS2812FX::get_external_buffer(); + for (int i = 0; i < 10; i++) { + external_buffer[i] = CRGB::White; + } + } +}; \ No newline at end of file diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h new file mode 100644 index 0000000000..67643f6eef --- /dev/null +++ b/usermods/Tubes/beats.h @@ -0,0 +1,72 @@ +#pragma once + +#include "timer.h" + +#define DEFAULT_BPM 120 + +typedef uint32_t BeatFrame_24_8; // 24:8 bitwise float + +// Regulates the beat counter, running patterns at 256 "fracs" per beat +class BeatController { + public: + accum88 bpm = 0; + BeatFrame_24_8 frac; + uint32_t accum = 0; + uint32_t micros_per_frac; + + void setup() + { + globalTimer.setup(); + + // Starts in phrase 1 + this->sync(DEFAULT_BPM << 8, 0); + } + + void update() + { + globalTimer.update(); + + // Maintains an accumulator with 14 bits of precision + this->accum += globalTimer.delta_micros << 8; + while (this->accum > this->micros_per_frac) { + this->frac++; + this->accum -= this->micros_per_frac; + } + } + + void sync(accum88 bpm, BeatFrame_24_8 frac) { + accum88 last_bpm = this->bpm; + this->bpm = bpm; + this->frac = frac; + this->accum = 0; + + this->micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); + + if (last_bpm != this->bpm) + this->print_bpm(); + } + + void set_bpm(accum88 bpm) { + this->sync(bpm, this->frac); + } + + void adjust_bpm(saccum78 bpm) { + this->sync(this->bpm + bpm, this->frac); + } + + void start_phrase() { + this->frac &= -0xFFF; + this->accum = 0; + } + + void print_bpm() { + Serial.print(this->bpm >> 8); + uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(F(".")); + if (frac < 10) + Serial.print(F("0")); + Serial.print(frac); + Serial.println(F("bpm")); + } + +}; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h new file mode 100644 index 0000000000..eb31bdbab3 --- /dev/null +++ b/usermods/Tubes/debug.h @@ -0,0 +1,56 @@ +#pragma once + + +class DebugController { + public: + PatternController *controller; + LEDs *strip; + Radio *radio; + uint32_t lastPhraseTime; + uint32_t lastFrame; + + DebugController(PatternController *controller) { + this->controller = controller; + this->strip = controller->led_strip; + this->radio = controller->radio; + } + + void setup() + { + this->lastPhraseTime = globalTimer.now_micros; + this->lastFrame = (uint32_t)-1; + } + + void update() + { + EVERY_N_MILLISECONDS( 10000 ) { + Serial.print(F("Free memory: ")); + Serial.println( freeMemory() ); + } + + // Show the beat on the master OR if debugging + + if (this->controller->options.debugging) { + uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; + this->strip->leds[p1] = CRGB::White; + + uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); + this->strip->leds[p2] = CRGB::White; + + uint8_t p3 = scale8(this->controller->radio->masterTubeId, this->strip->num_leds-1); + if (p3 == p2) { + this->strip->leds[p3] = CRGB::Green; + } else { + this->strip->leds[p3] = CRGB::Yellow; + } + if (this->radio->radioRestarts) { + this->strip->leds[1] = CRGB::Red; + } + } + + if (this->radio->radioFailures && !this->radio->radioRestarts) { + this->strip->leds[0] = CRGB::Red; + } + + } +}; diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h new file mode 100644 index 0000000000..33467b9869 --- /dev/null +++ b/usermods/Tubes/options.h @@ -0,0 +1,83 @@ +#pragma once + +typedef enum SyncMode { + All=0, + SinDrift=1, + Pulse=2, + Swing=3, + SwingDrift=4, +} SyncMode; + +typedef enum Duration: uint8_t { + ShortDuration=0, + MediumDuration=10, + LongDuration=20, + ExtraLongDuration=30, +} Duration; + +typedef enum Energy: uint8_t { + LowEnergy=0, + MediumEnergy=10, + HighEnergy=20, +} Energy; + +typedef struct ControlParameters { + public: + Duration duration=MediumDuration; + Energy energy=LowEnergy; + + ControlParameters(Duration duration=MediumDuration, Energy energy=LowEnergy) { + this->duration=duration; + this->energy=energy; + }; + +} ControlParams; + +typedef enum PenMode: uint8_t { + Draw=0, + Erase=1, + Blend=2, + Invert=3, + White=4, + Black=5, + Brighten=6, + Darken=7, + Flicker=8, +} PenMode; + +typedef enum EffectMode: uint8_t { + None=0, + Glitter=1, + Bubble=2, + Beatbox1=3, + Beatbox2=4, + Spark=5, + Flash=6, +} EffectMode; + +typedef enum BeatPulse: uint8_t { + Continuous=0, + Eighth=1, + Quarter=2, + Half=4, + Beat=8, + TwoBeats=16, + Measure=32, + TwoMeasures=64, + Phrase=128, +} BeatPulse; + +class EffectParameters { + public: + EffectMode effect; + PenMode pen=Draw; + BeatPulse beat=Beat; + uint8_t chance=255; + + EffectParameters(EffectMode effect=None, PenMode pen=Draw, BeatPulse beat=Beat, uint8_t chance=255) { + this->effect=effect; + this->pen=pen; + this->beat=beat; + this->chance=chance; + }; +}; diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h new file mode 100644 index 0000000000..77fdf883ed --- /dev/null +++ b/usermods/Tubes/pattern.h @@ -0,0 +1,187 @@ +#pragma once + +#include "virtual_strip.h" + +void rainbow(VirtualStrip *strip) +{ + // FastLED's built-in rainbow generator + fill_rainbow( strip->leds, strip->num_leds, strip->hue, 3); +} + +void palette_wave(VirtualStrip *strip) +{ + // FastLED's built-in rainbow generator + uint8_t hue = strip->hue; + for (uint8_t i=0; i < strip->num_leds; i++) { + CRGB c = strip->palette_color(i, hue); + nscale8x3(c.r, c.g, c.b, sin8(hue*8)); + strip->leds[i] = c; + hue++; + } +} + +void particleTest(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Black); + fill_solid( strip->leds, 2, strip->palette_color(0, strip->hue)); +} + +void solidBlack(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Black); +} + +void solidWhite(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::White); +} + +void solidRed(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Red); +} + +void solidBlue(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Blue); +} + +void confetti(VirtualStrip *strip) +{ + strip->darken(2); + + int pos = random16(strip->num_leds); + strip->leds[pos] += strip->palette_color(random8(64), strip->hue); +} + +uint16_t random_offset = random16(); + +void biwave(VirtualStrip *strip) +{ + uint16_t l = strip->frame * 16; + l = sin16( l + random_offset ) + 32768; + + uint16_t r = strip->frame * 32; + r = cos16( r + random_offset ) + 32768; + + uint8_t p1 = scaled16to8(l, 0, strip->num_leds-1); + uint8_t p2 = scaled16to8(r, 0, strip->num_leds-1); + + if (p2 < p1) { + uint16_t t = p1; + p1 = p2; + p2 = t; + } + + strip->fill(CRGB::Black); + for (uint16_t p = p1; p <= p2; p++) { + strip->leds[p] = strip->palette_color(p*2, strip->hue*3); + } +} + +void sinelon(VirtualStrip *strip) +{ + // a colored dot sweeping back and forth, with fading trails + strip->darken(30); + + int pos = scale16(sin16( strip->frame << 5 ) + 32768, strip->num_leds-1); // beatsin16 re-implemented + strip->leds[pos] += strip->hue_color(); +} + +void bpm_palette(VirtualStrip *strip) +{ + uint8_t beat = strip->bpm_sin16(64, 255); + for (int i = 0; i < strip->num_leds; i++) { + CRGB c = strip->palette_color(i*2, strip->hue); + nscale8x3(c.r, c.g, c.b, beat-strip->hue+(i*10)); + strip->leds[i] = c; + } +} + +void bpm(VirtualStrip *strip) +{ + // colored stripes pulsing at a defined Beats-Per-Minute (BPM) + CRGBPalette16 palette = PartyColors_p; + + uint8_t beat = strip->bpm_sin16(64, 255); + for (int i = 0; i < strip->num_leds; i++) { + strip->leds[i] = ColorFromPalette(palette, strip->hue+(i*2), beat-strip->hue+(i*10)); + } +} + +void juggle(VirtualStrip *strip) +{ + // eight colored dots, weaving in and out of sync with each other + strip->darken(5); + + byte dothue = 0; + for( int i = 0; i < 8; i++) { + CRGB c = strip->palette_color(dothue + strip->hue); + // c = CHSV(dothue, 200, 255); + strip->leds[beatsin16( i+7, 0, strip->num_leds-1 )] |= c; + dothue += 32; + } +} + +uint8_t noise[MAX_VIRTUAL_LEDS]; + +void fillnoise8(uint32_t frame, uint8_t num_leds) { + uint16_t scale = 17; + uint8_t dataSmoothing = 240; + + for (int i = 0; i < num_leds; i++) { + uint8_t data = inoise8(i * scale, frame>>2); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + data = qsub8(data,16); + data = qadd8(data,scale8(data,39)); + + uint8_t olddata = noise[i]; + uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); + noise[i] = newdata; + } +} + +void drawNoise(VirtualStrip *strip) +{ + // generate noise data + fillnoise8(strip->frame >> 2, strip->num_leds); + + for(int i = 0; i < strip->num_leds; i++) { + CRGB color = strip->palette_color(noise[i], strip->hue); + strip->leds[i] = color; + } +} + +typedef struct { + BackgroundFn backgroundFn; + ControlParameters control; +} PatternDef; + + +// List of patterns to cycle through. Each is defined as a separate function below. +PatternDef gPatterns[] = { + {drawNoise, {ShortDuration}}, + {drawNoise, {ShortDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {LongDuration}}, + {drawNoise, {LongDuration}}, + {rainbow, {ShortDuration}}, + {confetti, {ShortDuration}}, + {confetti, {MediumDuration}}, + + {juggle, {ShortDuration}}, + {bpm, {ShortDuration}}, + {bpm, {MediumDuration, HighEnergy}}, + {palette_wave, {ShortDuration}}, + {palette_wave, {MediumDuration}}, + {bpm_palette, {ShortDuration}}, + {bpm_palette, {MediumDuration, HighEnergy}} +}; + +/* +*/ +const uint8_t gPatternCount = ARRAY_SIZE(gPatterns); diff --git a/usermods/Tubes/timer.h b/usermods/Tubes/timer.h new file mode 100644 index 0000000000..22b00ab482 --- /dev/null +++ b/usermods/Tubes/timer.h @@ -0,0 +1,67 @@ +#pragma once + +class GlobalTimer { + public: + uint32_t now_millis; + uint32_t now_micros; + uint32_t last_micros; + uint32_t last_millis; + uint32_t delta_micros; + uint32_t delta_millis; + + void setup() + { + this->last_millis = this->now_millis = millis(); + this->last_micros = this->now_micros = micros(); + } + + void update() + { + this->last_millis = this->now_millis; + this->now_millis = millis(); + this->delta_millis = this->now_millis - this->last_millis; + + this->last_micros = this->now_micros; + this->now_micros = micros(); + this->delta_micros = this->now_micros - this->last_micros; + } +}; + +GlobalTimer globalTimer; + + + +class Timer { + public: + uint32_t markTime; + + void start(uint32_t duration_ms) { + this->markTime = globalTimer.now_millis + duration_ms; + } + + void stop() { + this->start(0); + } + + uint32_t since_mark() { + if (globalTimer.now_millis < this->markTime) + return 0; + return globalTimer.now_millis - this->markTime; + } + + void snooze(uint32_t duration_ms) { + while (this->markTime < globalTimer.now_millis) + this->markTime += duration_ms; + } + + bool ended() { + return globalTimer.now_millis > this->markTime; + } + + bool every(uint32_t duration_ms) { + if (!this->ended()) + return 0; + this->snooze(duration_ms); + return 1; + } +}; \ No newline at end of file diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h new file mode 100644 index 0000000000..25cbc7a163 --- /dev/null +++ b/usermods/Tubes/util.h @@ -0,0 +1,16 @@ +#pragma once + +#include "wled.h" + +uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16( v, rangewidth ); + uint16_t result = lowest + scaledbeat; + return result; +} + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +#define __ESP32__ +#define USTD_OPTION_FS_FORCE_NO_FS +#include \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h new file mode 100644 index 0000000000..177f64c14f --- /dev/null +++ b/usermods/Tubes/virtual_strip.h @@ -0,0 +1,206 @@ +#pragma once + +#include "util.h" +#include "options.h" +#include "beats.h" + +#define DEFAULT_FADE_SPEED 100 +#define MAX_VIRTUAL_LEDS 150 + +class VirtualStrip; +typedef void (*BackgroundFn)(VirtualStrip *strip); + +class Background { + public: + BackgroundFn animate; + CRGBPalette16 palette; + SyncMode sync=All; +}; + +typedef enum VirtualStripFade { + Steady=0, + FadeIn=1, + FadeOut=2, + Dead=99, +} VirtualStripFade; + +BeatFrame_24_8 swing(BeatFrame_24_8 frame) { + uint16_t fr = (frame & 0x3FF); // grab 4 beats + if (fr < 256) + fr = ease8InOutApprox(fr) << 2; + else + fr = 0x3FF; + + return (frame & 0xFC00) + fr; // recompose it +} + +class VirtualStrip { + const static uint16_t DEF_BRIGHT = 192; + + public: + CRGB leds[MAX_VIRTUAL_LEDS]; + uint8_t num_leds; + uint8_t brightness; + + // Fade in/out + VirtualStripFade fade; + uint16_t fader; + uint8_t fade_speed; + + // Pattern parameters + Background background; + uint32_t frame; + uint8_t beat; + uint16_t beat16; // 8 bits of beat and 8 bits of fractional + uint8_t hue; + bool beat_pulse; + int bps = 0; + + VirtualStrip(uint8_t num_leds) + { + this->fade = Dead; + this->num_leds = num_leds; + } + + void load(Background &background, uint8_t fade_speed=DEFAULT_FADE_SPEED) + { + this->background = background; + this->fade = FadeIn; + this->fader = 0; + this->fade_speed = fade_speed; + this->brightness = DEF_BRIGHT; + } + + void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) + { + if (this->fade == Dead) + return; + this->fade = FadeOut; + this->fade_speed = fade_speed; + } + + void darken(uint8_t amount=10) + { + fadeToBlackBy( this->leds, this->num_leds, amount); + } + + void fill(CRGB crgb) + { + fill_solid( this->leds, this->num_leds, crgb); + } + + void update(BeatFrame_24_8 frame, uint8_t beat_pulse) + { + if (this->fade == Dead) + return; + + this->frame = frame; + + switch (this->background.sync) { + case All: + break; + + case SinDrift: + // Drift slightly + this->frame = frame + (beatsin16( 5 ) >> 6); + break; + + case Swing: + // Swing the beat + this->frame = swing(frame); + break; + + case SwingDrift: + // Swing the beat AND drift slightly + this->frame = swing(frame) + (beatsin16( 5 ) >> 6); + break; + + case Pulse: + // Pulsing from 30 - 210 brightness + this->brightness = scale8(beatsin8( 10 ), 180) + 30; + break; + } + this->hue = (this->frame >> 4) % 256; + this->beat = (this->frame >> 8) % 16; + this->beat_pulse = beat_pulse; + + // Animate this virtual strip + this->background.animate(this); + + switch (this->fade) { + case Steady: + case Dead: + break; + + case FadeIn: + if (65535 - this->fader < this->fade_speed) { + this->fader = 65535; + this->fade = Steady; + } else { + this->fader += this->fade_speed; + } + break; + + case FadeOut: + if (this->fader < this->fade_speed) { + this->fader = 0; + this->fade = Dead; + return; + } else { + this->fader -= this->fade_speed; + } + break; + } + } + + CRGB palette_color(uint8_t c, uint8_t offset=0) { + return ColorFromPalette( this->background.palette, c + offset ); + } + + CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { + return CHSV(this->hue + offset, saturation, value); + } + + void blend(CRGB strip[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { + if (this->fade == Dead) + return; + + brightness = scale8(this->brightness, brightness); + + for (unsigned i=0; i < num_leds; i++) { +#ifdef DOUBLED + uint8_t pos = (2*i + 1) % this->num_leds; // slope of line is fixed right now at 2:1 +#else + uint8_t pos = i; +#endif + + CRGB c = this->leds[pos]; + +#ifdef DOUBLED + CRGB c1 = this->leds[pos-1]; + CRGB c2 = this->leds[pos+1]; + nblend(c1, c, 128); + nblend(c, c2, 128); + nblend(c, c1, 128); // C is now a weighted average of the three virtual pixels +#endif + + nscale8x3(c.r, c.g, c.b, brightness); + nscale8x3(c.r, c.g, c.b, this->fader>>8); + if (overwrite) + strip[i] = c; + else + strip[i] |= c; + } + } + + uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) + { + return scaled16to8(sin16( this->frame << 7 ) + 32768, lowest, highest); + } + + uint8_t bpm_cos16( uint16_t lowest=0, uint16_t highest=65535 ) + { + return scaled16to8(cos16( this->frame << 7 ) + 32768, lowest, highest); + } + +}; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 301c9d9039..39a10e0aa4 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4499,7 +4499,6 @@ uint16_t mode_aurora(void) { return FRAMETIME; } -<<<<<<< HEAD static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;sx=24,pal=50,1d"; // WLED-SR effects @@ -5280,6 +5279,20 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;!,!,!;!;2d"; +uint16_t WS2812FX::mode_external(void) { + // uint8_t segment_id = strip.getMainSegmentId(); + uint16_t length = strip.getLengthTotal(); + + for (int i = 0, p = 0; i < length; i++, p++) { + if (p >= EXTERNAL_BUFFER_SIZE) { + p = 0; + } + // strip.setPixelColor(i, color_from_palette(external_buffer[p], true, PALETTE_SOLID_WRAP, 0)); + strip.setPixelColor(i, external_buffer[p]); + } +} +static const char _data_FX_MODE_TUBES_NOISE[] PROGMEM = "External!@!,;!,!,;!;1d"; + //////////////////////////////// // 2D Polar Lights // //////////////////////////////// @@ -7448,7 +7461,8 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE); addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); - addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); + // addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); + addEffect(FX_MODE_EXTERNAL, &mode_external, _data_FX_MODE_EXTERNAL); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); // --- 2D non-audio effects --- @@ -7514,7 +7528,6 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); } -======= @@ -7558,4 +7571,3 @@ uint16_t WS2812FX::mode_tubes_moise(void) { return FRAMETIME; } ->>>>>>> 3b8e5a2f (Initial port of Tubes - palettes and noise functions) diff --git a/wled00/const.h b/wled00/const.h index 322c32b99a..a04a31c802 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -80,6 +80,7 @@ #define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h" #define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h #define USERMOD_ID_AUDIOREACTIVE 31 //Usermod "audioreactive.h" +#define USERMOD_ID_TUBES 32 //Usermod "usermod_tubes.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index d63730d45b..0c37efe320 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -136,6 +136,10 @@ #include "../usermods/audioreactive/audio_reactive.h" #endif +#ifdef USERMOD_TUBES +#include "../usermods/Tubes/Tubes.h" +#endif + void registerUsermods() { /* @@ -259,4 +263,9 @@ void registerUsermods() #ifdef USERMOD_AUDIOREACTIVE usermods.add(new AudioReactive()); #endif + + #ifdef USERMOD_TUBES + usermods.add(new TubesUsermod()); + #endif + } From e3f41449bfefd8da29ab8f448b5faa4d015c445c Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 6 Jul 2022 00:03:15 -0700 Subject: [PATCH 028/263] Light tubes are up and running --- usermods/Tubes/Tubes.h | 72 +++- usermods/Tubes/controller.h | 686 +++++++++++++++++++++++++++++++++ usermods/Tubes/debug.h | 4 +- usermods/Tubes/effects.h | 155 ++++++++ usermods/Tubes/global_state.h | 54 +++ usermods/Tubes/led_strip.h | 60 +++ usermods/Tubes/particle.h | 227 +++++++++++ usermods/Tubes/radio.h | 252 ++++++++++++ usermods/Tubes/virtual_strip.h | 13 - 9 files changed, 1488 insertions(+), 35 deletions(-) create mode 100644 usermods/Tubes/controller.h create mode 100644 usermods/Tubes/effects.h create mode 100644 usermods/Tubes/global_state.h create mode 100644 usermods/Tubes/led_strip.h create mode 100644 usermods/Tubes/particle.h create mode 100644 usermods/Tubes/radio.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 2243004f91..cb167cedd0 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -7,27 +7,28 @@ // #define MASTERCONTROL -#define MASTER_PIN 6 -#define NUM_LEDS 64 +#define MASTER_PIN 6 -#define USERADIO +// #define USERADIO #include "FX.h" #include "beats.h" #include "virtual_strip.h" +#include "led_strip.h" -// #include "controller.h" -// #include "radio.h" -// #include "debug.h" +#include "controller.h" +#include "radio.h" +#include "debug.h" class TubesUsermod : public Usermod { private: BeatController beats; - // Radio radio; - // PatternController controller(NUM_LEDS, &beats, &radio); - // DebugController debug(&controller); + Radio radio; + PatternController controller = PatternController(MAX_REAL_LEDS, &beats, &radio); + DebugController debug = DebugController(&controller); + int* master = NULL; /* master.h deleted */ void randomize(long seed) { for (int i = 0; i < seed % 16; i++) { @@ -41,8 +42,8 @@ class TubesUsermod : public Usermod { // Start timing globalTimer.setup(); beats.setup(); - // controller.setup(0); - // debug.setup(); + controller.setup(0); + debug.setup(); } void loop() @@ -51,16 +52,45 @@ class TubesUsermod : public Usermod { randomize(random(INT_MAX)); } - beats.update(); // ~30us - // controller.update(); // radio: 0-3000us patterns: 0-3000us lcd: ~50000us - // debug.update(); // ~25us + beats.update(); + controller.update(); + debug.update(); // Draw after everything else is done - // controller.led_strip->update(master != NULL); // ~25us - - CRGB *external_buffer = WS2812FX::get_external_buffer(); - for (int i = 0; i < 10; i++) { - external_buffer[i] = CRGB::White; - } + controller.led_strip->update(master != NULL); // ~25us } -}; \ No newline at end of file +}; + + + + +/* +LIST OF GOOD PATTERNS + +Aurora +Dynamic Smooth +Blends +Colortwinkles +Fillnoise - maybe +Fireworks +Fireworks Starburst +Flow +Gradient - maybe +Juggle - maybe +Lake +Meteor Smooth - maybe +Noise 2 +Noise 4 +Pacifica +Palette - maybe +Phased - maybe +Plasma +Ripple +Running Dual +Saw - maybe +Sinelon Dual - maybe +Tetrix - maybe +Twinklecat +Twinkleup + +*/ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h new file mode 100644 index 0000000000..01539dc63c --- /dev/null +++ b/usermods/Tubes/controller.h @@ -0,0 +1,686 @@ +#pragma once + +#include "beats.h" + +#include "pattern.h" +#include "palettes.h" +#include "effects.h" +#include "global_state.h" +#include "radio.h" + +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 144; + +const static CommandId COMMAND_UPDATE = 0x411; +const static CommandId COMMAND_NEXT = 0x321; +const static CommandId COMMAND_RESET = 0x911; +const static CommandId COMMAND_FIREWORK = 0xFFF; +const static CommandId COMMAND_HELLO = 0x000; +const static CommandId COMMAND_OPTIONS = 0x123; +const static CommandId COMMAND_BRIGHTNESS = 0x888; + + +typedef struct { + bool debugging; + uint8_t brightness; +} ControllerOptions; + +#define NEXT_PATTERN_TIME 53000 +#define NEXT_PALETTE_TIME 27000 + +#define NUM_VSTRIPS 3 + +#define DEBOUNCE_TIME 40 + +class Button { + public: + Timer debounceTimer; + uint8_t pin; + bool lastPressed = false; + + void setup(uint8_t pin) { + this->pin = pin; + pinMode(pin, INPUT_PULLUP); + this->debounceTimer.start(0); + } + + bool pressed() { + if (digitalRead(this->pin) == HIGH) { + return !this->debounceTimer.ended(); + } + + this->debounceTimer.start(DEBOUNCE_TIME); + return true; + } + + bool triggered() { + // Triggers BOTH low->high AND high->low + bool p = this->pressed(); + bool lp = this->lastPressed; + this->lastPressed = p; + return p != lp; + } +}; + +class PatternController : public MessageReceiver { + public: + const static int FRAMES_PER_SECOND = 300; // how often we animate, in frames per second + const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds + + uint8_t num_leds; + VirtualStrip *vstrips[NUM_VSTRIPS]; + uint8_t next_vstrip = 0; + bool isMaster = false; + + Timer graphicsTimer; + Timer updateTimer; + Timer slaveTimer; + +#ifdef USELCD + Lcd *lcd; +#endif + LEDs *led_strip; + BeatController *beats; + Radio *radio; + Effects *effects; + + ControllerOptions options; + char key_buffer[20] = {0}; + + Energy energy=LowEnergy; + TubeState current_state; + TubeState next_state; + + PatternController(uint8_t num_leds, BeatController *beats, Radio *radio) { + this->num_leds = num_leds; +#ifdef USELCD + this->lcd = new Lcd(); +#endif + this->led_strip = new LEDs(num_leds); + this->beats = beats; + this->radio = radio; + this->effects = new Effects(); + + for (uint8_t i=0; i < NUM_VSTRIPS; i++) { +#ifdef DOUBLED + this->vstrips[i] = new VirtualStrip(num_leds * 2 + 1); +#else + this->vstrips[i] = new VirtualStrip(num_leds); +#endif + } + + } + + void setup(bool isMaster) + { + this->isMaster = isMaster; + this->options.debugging = false; + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + +#ifdef USELCD + this->lcd->setup(); +#endif + this->set_next_pattern(0); + this->set_next_palette(0); + this->set_next_effect(0); + this->next_state.pattern_phrase = 0; + this->next_state.palette_phrase = 0; + this->next_state.effect_phrase = 0; + Serial.println(F("Patterns: ok")); + + this->radio->setup(this->isMaster); + this->radio->sendCommand(COMMAND_HELLO); + + this->slaveTimer.start(RADIO_SENDPERIOD * 3); // Assume we're a slave at first, just listen for a master. + this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to + } + + void update() + { + this->read_keys(); + + // If master has expired, clear masterId + if (this->radio->masterTubeId && this->slaveTimer.ended()) { + Serial.println(F("I have no master")); + this->radio->masterTubeId = 0; + } + + // Update patterns to the beat + this->update_beat(); + + uint16_t phrase = this->current_state.beat_frame >> 12; + if (phrase >= this->next_state.pattern_phrase) { + this->load_pattern(this->next_state); + this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + } + if (phrase >= this->next_state.palette_phrase) { + this->load_palette(this->next_state); + this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + } + if (phrase >= this->next_state.effect_phrase) { + this->load_effect(this->next_state); + this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + } + + // If alone or master, send out updates + if (!this->radio->masterTubeId and this->updateTimer.ended()) { + this->send_update(); + } + + this->radio->receiveCommands(this); + + if (this->graphicsTimer.every(REFRESH_PERIOD)) { + this->updateGraphics(); + } + +#ifdef USELCD + if (this->lcd->active) { + this->lcd->size(1); + this->lcd->write(0,56, this->current_state.beat_frame); + this->lcd->write(80,56, this->x_axis); + this->lcd->write(100,56, this->y_axis); + this->lcd->show(); + + this->lcd->update(); + } +#endif + } + + void restart_phrase() { + this->beats->start_phrase(); + this->update_beat(); + this->send_update(); + } + + void set_phrase_position(uint8_t pos) { + this->beats->sync(this->beats->bpm, (this->beats->frac & -0xFFF) + (pos<<8)); + this->update_beat(); + this->send_update(); + } + + void set_tapped_bpm(accum88 bpm, uint8_t pos=15) { + // By default, restarts at 15th beat - because this is the end of a tap + this->beats->sync(bpm, (this->beats->frac & -0xFFF) + (pos<<8)); + this->update_beat(); + this->send_update(); + } + + void update_beat() { + this->current_state.bpm = this->next_state.bpm = this->beats->bpm; + this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) + if (this->current_state.bpm >= 125>>8) + this->energy = HighEnergy; + else if (this->current_state.bpm > 120>>8) + this->energy = MediumEnergy; + else + this->energy = LowEnergy; + } + + void send_update() { + this->current_state.print(); + Serial.print(F(" ")); + + if (this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state))) { + this->radio->radioFailures = 0; + this->updateTimer.snooze(RADIO_SENDPERIOD); + } else { + // might have been a collision. Back off by a small amount determined by ID + Serial.println(F("Radio update failed")); + this->updateTimer.snooze( this->radio->tubeId & 0x7F ); + this->radio->radioFailures++; + if (this->radio->radioFailures > 100) { + this->radio->setup(this->isMaster); + this->radio->radioRestarts++; + } + } + + uint16_t phrase = this->current_state.beat_frame >> 12; + Serial.print(F(" ")); + Serial.print(this->next_state.pattern_phrase - phrase); + Serial.print(F("P ")); + Serial.print(this->next_state.palette_phrase - phrase); + Serial.print(F("C ")); + Serial.print(this->next_state.effect_phrase - phrase); + Serial.print(F("E: ")); + this->next_state.print(); + Serial.print(F(" ")); + this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + Serial.println(); + } + + void background_changed() { + this->update_background(); + this->current_state.print(); + Serial.println(); + } + + void load_pattern(TubeState &tube_state) { + if (this->current_state.pattern_id == tube_state.pattern_id + && this->current_state.pattern_sync_id == tube_state.pattern_sync_id) + return; + + this->current_state.pattern_phrase = tube_state.pattern_phrase; + this->current_state.pattern_id = tube_state.pattern_id % gPatternCount; + this->current_state.pattern_sync_id = tube_state.pattern_sync_id; + + Serial.print(F("Change pattern ")); + this->background_changed(); + } + + uint16_t set_next_pattern(uint16_t phrase) { + uint8_t pattern_id = random8(gPatternCount); + PatternDef def = gPatterns[pattern_id]; + if (def.control.energy > this->energy) { + pattern_id = 0; + def = gPatterns[0]; + } + + this->next_state.pattern_id = pattern_id; + this->next_state.pattern_sync_id = this->randomSyncMode(); + + switch (def.control.duration) { + case ShortDuration: return random8(5,15); + case MediumDuration: return random8(15,25); + case LongDuration: return random8(35,45); + case ExtraLongDuration: return random8(70, 100); + } + return 5; + } + + void load_palette(TubeState &tube_state) { + if (this->current_state.palette_id == tube_state.palette_id) + return; + + this->current_state.palette_phrase = tube_state.palette_phrase; + this->_load_palette(tube_state.palette_id); + } + + void _load_palette(uint8_t palette_id) { + this->current_state.palette_id = palette_id % gGradientPaletteCount; + + Serial.print(F("Change palette")); + this->background_changed(); + } + + uint16_t set_next_palette(uint16_t phrase) { + this->next_state.palette_id = random8(gGradientPaletteCount); + return random8(4,40); + } + + void load_effect(TubeState &tube_state) { + if (this->current_state.effect_params.effect == tube_state.effect_params.effect && + this->current_state.effect_params.pen == tube_state.effect_params.pen && + this->current_state.effect_params.chance == tube_state.effect_params.chance) + return; + + this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; + this->_load_effect(tube_state.effect_params); + } + + void _load_effect(EffectParameters params) { + this->current_state.effect_params = params; + + Serial.print(F("Change effect ")); + this->current_state.print(); + Serial.println(); + + this->effects->load(this->current_state.effect_params); + } + + uint16_t set_next_effect(uint16_t phrase) { + EffectDef def = gEffects[random8(gEffectCount)]; + if (def.control.energy > this->energy) + def = gEffects[0]; + + this->next_state.effect_params = def.params; + + switch (def.control.duration) { + case ShortDuration: return 3; + case MediumDuration: return 6; + case LongDuration: return 10; + case ExtraLongDuration: return 20; + } + return 1; + } + + void update_background() { + Background background; + background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; + background.palette = gGradientPalettes[this->current_state.palette_id]; + background.sync = (SyncMode)this->current_state.pattern_sync_id; + + // re-use virtual strips to prevent heap fragmentation + for (uint8_t i = 0; i < NUM_VSTRIPS; i++) { + this->vstrips[i]->fadeOut(); + } + this->vstrips[this->next_vstrip]->load(background); + this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; + } + + void optionsChanged() { + if (this->isMaster) { + this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); + } + } + + void setBrightness(uint8_t brightness) { + Serial.print(F("brightness ")); + Serial.println(brightness); + + this->options.brightness = brightness; + this->optionsChanged(); + } + + void setDebugging(bool debugging) { + Serial.print(F("debugging ")); + Serial.println(debugging); + + this->options.debugging = debugging; + this->optionsChanged(); + } + + SyncMode randomSyncMode() { + uint8_t r = random8(128); + if (r < 40) + return SinDrift; + if (r < 65) + return Pulse; + if (r < 72) + return Swing; + if (r < 84) + return SwingDrift; + return All; + } + + void updateGraphics() { + static BeatFrame_24_8 lastFrame = 0; + BeatFrame_24_8 beat_frame = this->current_state.beat_frame; + + uint8_t beat_pulse = 0; + for (int i = 0; i < 8; i++) { + if ( (beat_frame >> (5+i)) != (lastFrame >> (5+i))) + beat_pulse |= 1<vstrips[i]; + if (vstrip->fade == Dead) + continue; + + // Remember the first strip + if (first_strip == NULL) + first_strip = vstrip; + + vstrip->update(beat_frame, beat_pulse); + vstrip->blend(this->led_strip->leds, this->led_strip->num_leds, this->options.brightness, vstrip == first_strip); + } + + this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); + this->effects->draw(this->led_strip->leds, this->num_leds); + } + + virtual void acknowledge() { + addFlash(); + } + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + if (fromId) { + Serial.print(F("From ")); + Serial.print(fromId); + Serial.print(F(": ")); + } + + switch (command) { + case COMMAND_FIREWORK: + Serial.print(F("fireworks")); + this->acknowledge(); + return; + + case COMMAND_RESET: + Serial.print(F("reset")); + return; + + case COMMAND_BRIGHTNESS: { + uint8_t *bright = (uint8_t *)data; + this->setBrightness(*bright); + return; + } + + case COMMAND_HELLO: + Serial.print(F("hello")); + this->updateTimer.stop(); + return; + + case COMMAND_OPTIONS: { + Serial.print(F("options")); + memcpy(&this->options, data, sizeof(this->options)); + return; + } + + case COMMAND_NEXT: { + Serial.print(F(" next ")); + if (fromId < this->radio->masterTubeId) { + Serial.print(F(" (ignoring)")); + return; + } + + memcpy(&this->next_state, data, sizeof(TubeState)); + this->next_state.print(); + Serial.print(F(" (obeying)")); + return; + } + + case COMMAND_UPDATE: { + Serial.print(F(" update ")); + if (fromId < this->radio->masterTubeId) { + Serial.print(F(" (ignoring)")); + return; + } + + TubeState state; + memcpy(&state, data, sizeof(TubeState)); + state.print(); + Serial.print(F(" (obeying)")); + + // Track the last time we received a message from our master + this->slaveTimer.start(RADIO_SENDPERIOD * 8); + + // Catch up to this state + this->load_pattern(state); + this->load_palette(state); + this->load_effect(state); + this->beats->sync(state.bpm, state.beat_frame); + return; + } + } + + Serial.print(F("UNKNOWN ")); + Serial.print(command, HEX); + } + + void read_keys() { + if (!Serial.available()) + return; + + char c = Serial.read(); + char *k = this->key_buffer; + uint8_t max = sizeof(this->key_buffer); + for (uint8_t i=0; *k && (i < max-1); i++) { + k++; + } + if (c == 10) { + this->keyboard_command(this->key_buffer); + this->key_buffer[0] = 0; + } else { + *k++ = c; + *k = 0; + } + } + + accum88 parse_number(char *s) { + uint16_t n=0, d=0; + + while (*s == ' ') + s++; + while (*s) { + if (*s < '0' || *s > '9') + break; + n = n*10 + (*s++ - '0'); + } + n = n << 8; + + if (*s == '.') { + uint16_t div = 1; + s++; + while (*s) { + if (*s < '0' || *s > '9') + break; + d = d*10 + (*s++ - '0'); + div *= 10; + } + d = (d << 8) / div; + } + return n+d; + } + + void keyboard_command(char *command) { + uint8_t b; + accum88 arg = this->parse_number(command+1); + + switch (command[0]) { + case 'f': + this->radio->sendCommandFrom(255, COMMAND_FIREWORK, NULL, 0); + this->onCommand(0, COMMAND_FIREWORK, NULL); + Serial.println(); + break; + + case 'i': + this->radio->resetId(arg >> 8); + break; + + case 'd': + this->setDebugging(!this->options.debugging); + break; + + case '-': + b = this->options.brightness; + while (*command++ == '-') + b -= 5; + this->setBrightness(b - 5); + break; + case '+': + b = this->options.brightness; + while (*command++ == '+') + b += 5; + this->setBrightness(b + 5); + return; + case 'l': + if (arg < 5*256) { + Serial.println(F("nope")); + return; + } + this->setBrightness(arg >> 8); + return; + + case 'b': + if (arg < 60*256) { + Serial.println(F("nope")); + return; + } + this->beats->set_bpm(arg); + this->update_beat(); + this->send_update(); + return; + + case 's': + this->beats->start_phrase(); + this->update_beat(); + this->send_update(); + return; + + case 'n': + this->force_next(); + return; + + case 'p': + this->next_state.pattern_phrase = 0; + this->next_state.pattern_id = arg >> 8; + this->next_state.pattern_sync_id = All; + this->update_next(); + return; + + case 'm': + this->next_state.pattern_phrase = 0; + this->next_state.pattern_id = this->current_state.pattern_id; + this->next_state.pattern_sync_id = arg >> 8; + this->update_next(); + return; + + case 'c': + this->next_state.palette_phrase = 0; + this->next_state.palette_id = arg >> 8; + this->update_next(); + return; + + case 'e': + this->next_state.effect_phrase = 0; + this->next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; + this->update_next(); + return; + + case '%': + this->next_state.effect_phrase = 0; + this->next_state.effect_params = this->current_state.effect_params; + this->next_state.effect_params.chance = arg; + this->update_next(); + return; + + case 'h': + // Pretend to receive a HELLO + this->onCommand(0, COMMAND_HELLO, NULL); + Serial.println(); + return; + + case 'g': + /* PARTICLES + for (int i=0; i< 10; i++) + addGlitter(); + */ + break; + + case '?': + Serial.println(F("b###.# - set bpm")); + Serial.println(F("s - start phrase")); + Serial.println(); + Serial.println(F("p### - patterns")); + Serial.println(F("m### - sync mode")); + Serial.println(F("c### - colors")); + Serial.println(F("e### - effects")); + Serial.println(); + Serial.println(F("i### - set ID")); + Serial.println(F("d - toggle debugging")); + Serial.println(F("l### - brightness")); + } + } + + void force_next() { + uint16_t phrase = this->current_state.beat_frame >> 12; + uint16_t next_phrase = min(this->next_state.pattern_phrase, min(this->next_state.palette_phrase, this->next_state.effect_phrase)) - phrase; + this->next_state.pattern_phrase -= next_phrase; + this->next_state.palette_phrase -= next_phrase; + this->next_state.effect_phrase -= next_phrase; + this->update_next(); + } + + void update_next() { + this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + } + +}; + + + +// What's interesting? +// c53 - clouds +// m4 - swing drift diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index eb31bdbab3..8f4717ab89 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,5 +1,8 @@ #pragma once +#include "controller.h" +#include "radio.h" + class DebugController { public: @@ -51,6 +54,5 @@ class DebugController { if (this->radio->radioFailures && !this->radio->radioRestarts) { this->strip->leds[0] = CRGB::Red; } - } }; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h new file mode 100644 index 0000000000..f4ad7532c2 --- /dev/null +++ b/usermods/Tubes/effects.h @@ -0,0 +1,155 @@ +#pragma once + +#include "util.h" +#include "particle.h" +#include "virtual_strip.h" + +void addGlitter(CRGB color=CRGB::White, PenMode pen=Draw) +{ + addParticle(new Particle(random16(), color, pen, 128)); +} + +void addSpark(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 64); + uint8_t r = random8(); + if (r > 128) + particle->velocity = r; + else + particle->velocity = -(128 + r); + addParticle(particle); +} + +void addBeatbox(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 256, drawBeatbox); + addParticle(particle); +} + +void addBubble(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 1024, drawPop); + particle->velocity = random16(0, 40) - 20; + addParticle(particle); +} + +void addFlash(CRGB color=CRGB::Blue, PenMode pen=Draw) +{ + addParticle(new Particle(random16(), color, pen, 256, drawFlash)); +} + +void addDrop(CRGB color, PenMode pen=Draw) +{ + Particle *particle = new Particle(65535, color, pen, 360); + particle->velocity = -500; + particle->gravity = -10; + addParticle(particle); +} + +class Effects { + public: + EffectMode effect=None; + PenMode pen=Draw; + BeatPulse beat; + uint8_t chance; + + void load(EffectParameters ¶ms) { + this->effect = params.effect; + this->pen = params.pen; + this->beat = params.beat; + this->chance = params.chance; + } + + void update(VirtualStrip *strip, BeatFrame_24_8 beat_frame, BeatPulse beat_pulse) { + if (!this->beat || beat_pulse & this->beat) { + + if (random8() <= this->chance) { + CRGB color = strip->palette_color(random8()); + + switch (this->effect) { + case None: + break; + + case Glitter: + addGlitter(color, this->pen); + break; + + case Beatbox1: + case Beatbox2: + addBeatbox(color, this->pen); + if (this->effect == Beatbox2) + addBeatbox(color, this->pen); + break; + + case Bubble: + addBubble(color, this->pen); + break; + + case Spark: + addSpark(color, this->pen); + break; + + case Flash: + addFlash(CRGB::White, this->pen); + break; + } + } + } + + this->animate(beat_frame, beat_pulse); + } + + void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { + unsigned int len = 0; /* PARTICLES particles.length(); */ + for (unsigned i=len; i > 0; i--) { + Particle *particle = particles[i-1]; + + particle->update(frame); + if (particle->age > particle->lifetime) { + delete particle; + /* PARTICLES particles.erase(i-1); */ + continue; + } + } + } + + void draw(CRGB strip[], uint8_t num_leds) { + uint8_t len = 0; /* PARTICLES particles.length(); */ + for (uint8_t i=0; idrawFn(particle, strip, num_leds); + } + } + +}; + + +typedef struct { + EffectParameters params; + ControlParameters control; +} EffectDef; + + +static const EffectDef gEffects[] = { + {{None}, {LongDuration}}, + {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, + {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, + {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, + {{Flash, Brighten, Phrase}, {MediumDuration, HighEnergy}}, + {{Flash, Darken, Measure}, {ShortDuration, LowEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Glitter, Brighten, Eighth, 80}, {MediumDuration, MediumEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {MediumDuration, HighEnergy}}, + {{Glitter, Darken, Eighth, 40}, {MediumDuration, LowEnergy}}, + + {{Glitter, Draw, Eighth, 10}, {LongDuration, LowEnergy}}, + {{Glitter, Draw, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Glitter, Invert, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Beatbox2, Black}, {MediumDuration, LowEnergy}}, + {{Beatbox2, Draw}, {ShortDuration, HighEnergy}}, + {{Bubble, Darken}, {MediumDuration, LowEnergy}}, + {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, + {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, +}; +const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h new file mode 100644 index 0000000000..a23a769aae --- /dev/null +++ b/usermods/Tubes/global_state.h @@ -0,0 +1,54 @@ +#pragma once + +#include "wled.h" +#include "beats.h" +#include "effects.h" + + +class TubeState { + public: + // Global clock: frames are defined as 1/64th of a beat + accum88 bpm = 0; // BPM in high 8 bits, fraction in low 8 bits + BeatFrame_24_8 beat_frame = 0; // current beat (24 bits) and fractional beat (8bits) + + uint16_t pattern_phrase; + uint8_t pattern_id; + uint8_t pattern_sync_id; + + uint16_t palette_phrase; + uint8_t palette_id; + + uint16_t effect_phrase; + EffectParameters effect_params; + + void print() { + uint16_t phrase = this->beat_frame >> 12; + Serial.print(F("[")); + Serial.print(phrase); + Serial.print(F(".")); + Serial.print((this->beat_frame >> 8) % 16); + Serial.print(F(" P")); + Serial.print(this->pattern_id); + Serial.print(F(",")); + Serial.print(this->pattern_sync_id); + Serial.print(F(" C")); + Serial.print(this->palette_id); + Serial.print(F(" E")); + Serial.print(this->effect_params.effect); + Serial.print(F(",")); + Serial.print(this->effect_params.pen); + Serial.print(F(",")); + Serial.print(this->effect_params.beat); + Serial.print(F(",")); + Serial.print(this->effect_params.chance); + Serial.print(F(" ")); + Serial.print(this->bpm >> 8); + uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(F(".")); + if (frac < 10) + Serial.print(F("0")); + Serial.print(frac); + Serial.print(F("bpm]")); + } + +}; diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h new file mode 100644 index 0000000000..ddbca2fd4b --- /dev/null +++ b/usermods/Tubes/led_strip.h @@ -0,0 +1,60 @@ +#pragma once + +#define USE_WLED +#include "wled.h" + +#define MAX_REAL_LEDS 64 + + +class LEDs { + public: + CRGB leds[MAX_REAL_LEDS]; + // CRGB led_array[MAX_REAL_LEDS]; + + const static int FRAMES_PER_SECOND = 300; // how often we refresh the strip, in frames per second + const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we refresh the strip, in milliseconds + int num_leds; + + uint16_t fps = 0; + + LEDs(int num_leds=MAX_REAL_LEDS) { + this->num_leds = num_leds; + } + + void setup() { + Serial.println((char *)F("LEDs: ok")); + } + + void reverse() { + for (int i=1; i<8; i++) { + CRGB c = this->leds[i]; + this->leds[i] = this->leds[16-i]; + this->leds[16-i] = c; + } + } + + void show() { + CRGB *external_buffer = WS2812FX::get_external_buffer(); + for (int i = 0; i < num_leds; i++) { + external_buffer[i] = leds[i]; + } + } + + void update(bool reverse=false) { + EVERY_N_MILLISECONDS( this->REFRESH_PERIOD ) { + // Update the LEDs + if (reverse) + this->reverse(); + show(); + this->fps++; + } + + EVERY_N_MILLISECONDS( 1000 ) { + if (this->fps < (FRAMES_PER_SECOND - 30)) { + Serial.print(this->fps); + Serial.println((char *)F(" fps!")); + } + this->fps = 0; + } + } +}; diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h new file mode 100644 index 0000000000..d49f10a1bb --- /dev/null +++ b/usermods/Tubes/particle.h @@ -0,0 +1,227 @@ +#pragma once + +#include "wled.h" +#include "beats.h" +#include "options.h" +// #include "ustd.h" + +#define MAX_PARTICLES 20 +#undef PARTICLE_PALETTES + +class Particle; + +typedef void (*ParticleFn)(Particle *particle, CRGB strip[], uint8_t num_leds); + +extern void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds); +extern void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds); + + +class Particle { + public: + BeatFrame_24_8 born; + BeatFrame_24_8 lifetime; + BeatFrame_24_8 age; + + uint16_t position = 0; + int16_t velocity = 0; + int16_t gravity = 0; + void (*die_fn)(Particle *particle) = NULL; + PenMode pen = Draw; + +#ifdef PARTICLE_PALETTES + CRGBPalette16 palette; // 48 bytes per particle!? +#endif + + CRGB color; + uint16_t brightness; + ParticleFn drawFn; + + Particle(uint16_t position, CRGB color=CRGB::White, PenMode pen=Draw, uint32_t lifetime=20000, ParticleFn drawFn=drawPoint) + { + this->age = 0; + this->position = position; + this->color = color; + this->pen = pen; + this->lifetime = lifetime; + this->brightness = (192<<8); + this->drawFn = drawFn; + } + + void update(BeatFrame_24_8 frame) + { + this->age = frame - this->born; + this->position = this->udelta16(this->position, this->velocity); + this->velocity = this->delta16(this->velocity, this->gravity); + } + + uint16_t age_frac16(BeatFrame_24_8 age) + { + if (age >= this->lifetime) + return 65535; + uint32_t a = age * 65536; + return a / this->lifetime; + } + + uint16_t udelta16(uint16_t x, int16_t dx) + { + if (dx > 0 && 65535-x < dx) + return 65335; + if (dx < 0 && x < -dx) + return 0; + return x + dx; + } + + int16_t delta16(int16_t x, int16_t dx) + { + if (dx > 0 && 32767-x < dx) + return 32767; + if (dx < 0 && x < -32767 - dx) + return -32767; + return x + dx; + } + + CRGB color_at(uint16_t age_frac) { + // Particles get dimmer with age + uint8_t a = age_frac >> 8; + uint8_t brightness = scale8((uint8_t)(this->brightness>>8), 255-a); + +#ifdef PARTICLE_PALETTES + // a black pattern actually means to use the current palette + if (this->color == CRGB(0,0,0)) + return ColorFromPalette(this->palette, a, brightness); +#endif + + uint8_t r = scale8(this->color.r, brightness); + uint8_t g = scale8(this->color.g, brightness); + uint8_t b = scale8(this->color.b, brightness); + return CRGB(r,g,b); + } + + void draw_with_pen(CRGB strip[], int pos, CRGB color) { + CRGB new_color; + + switch (this->pen) { + case Draw: + strip[pos] = color; + break; + + case Blend: + strip[pos] |= color; + break; + + case Erase: + strip[pos] &= color; + break; + + case Invert: + strip[pos] = -strip[pos]; + break; + + case Brighten: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + strip[pos] += new_color; + break; + } + + case Darken: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + strip[pos] -= new_color; + break; + } + + case Flicker: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + if (millis() % 2) + strip[pos] -= new_color; + else + strip[pos] += new_color; + break; + } + + case White: + strip[pos] = CRGB::White; + break; + + case Black: + strip[pos] = CRGB::Black; + break; + + } + } + +}; + +Particle* particles[5]; +/* PARTICLES +ustd::array particles = ustd::array(5); +*/ +BeatFrame_24_8 particle_beat_frame; + +void addParticle(Particle *particle) { + particle->born = particle_beat_frame; + /* PARTICLES + particles.add(particle); + if (particles.length() > MAX_PARTICLES) { + Particle *old_particle = particles[0]; + delete old_particle; + particles.erase(0); + } + */ +} + + +void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + for (int pos = 0; pos < num_leds; pos++) { + particle->draw_with_pen(strip, pos, c); + } +} + +void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + + uint16_t pos = scale16(particle->position, num_leds-1); + particle->draw_with_pen(strip, pos, c); +} + +void drawRadius(Particle *particle, CRGB strip[], uint8_t num_leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + for (int i = 0; i < radius; i++) { + uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; + nscale8(&c, 1, bright); + + uint8_t y = pos - i; + if (y >= 0 && y < num_leds) + particle->draw_with_pen(strip, y, c); + + if (i == 0) + continue; + + y = pos + i; + if (y >= 0 && y < num_leds) + particle->draw_with_pen(strip, y, c); + } +} + +void drawPop(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + uint16_t pos = scale16(particle->position, num_leds-1); + uint8_t radius = scale16((sin16(age_frac/2) - 32768) * 2, 8); + + drawRadius(particle, strip, num_leds, pos, radius, c); +} + +void drawBeatbox(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + uint16_t pos = scale16(particle->position, num_leds-1); + uint8_t radius = 5; + + drawRadius(particle, strip, num_leds, pos, radius, c, false); +} + diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h new file mode 100644 index 0000000000..86e3883c0c --- /dev/null +++ b/usermods/Tubes/radio.h @@ -0,0 +1,252 @@ +#ifndef RADIO_H +#define RADIO_H + +#include +#include + +#define RADIO_VERSION 1 + +#ifdef USERADIO +NRFLite _radio(Serial); +#endif + +const static uint8_t RADIO_TX_ID = 0; // Radio ID for both trans +const static uint8_t RADIO_RX_ID = 0; // Radio ID for both recv + +const static uint8_t PIN_RADIO_CE = 9; // hardware pins +const static uint8_t PIN_RADIO_CSN = 10; // hardware pins +const static uint8_t PIN_RADIO_MOSI = 11; // hardware pins +const static uint8_t PIN_RADIO_MISO = 12; // hardware pins +const static uint8_t PIN_RADIO_SCK = 13; // hardware pins + +#define RADIO_BITRATE NRFLite::BITRATE1MBPS // { BITRATE2MBPS, BITRATE1MBPS, BITRATE250KBPS } +#define RADIO_CHANNEL 100 + RADIO_VERSION // Channel hop with each version +#define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec + +class Radio; + +typedef uint16_t CommandId; +typedef uint8_t TubeId; + +#define MESSAGE_DATA_MAX_SIZE 25 +typedef struct { + CommandId command; + TubeId tubeId; + TubeId relayId; + byte data[MESSAGE_DATA_MAX_SIZE]; + uint16_t crc = 0; +} RadioMessage; + +class MessageReceiver { + public: + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + +uint16_t calculate_crc( byte *data, byte len ) { + + const unsigned long crc_table[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c + }; + + unsigned long crc = ~0L; + + for ( unsigned int index = 0 ; index < len ; ++index ) { + crc = crc_table[( crc ^ data[index] ) & 0x0f] ^ (crc >> 4); + crc = crc_table[( crc ^ ( data[index] >> 4 )) & 0x0f] ^ (crc >> 4); + crc = ~crc; + } + return crc & 65535; +} + +uint8_t newTubeId() { + return random(10, 250); // Leave room for master +} + +void printMessageData(RadioMessage &message, int size) { + Serial.print(sizeof(message.data)); + Serial.print(F(":")); + for (unsigned int i = 0; i < sizeof(message.data); i++) { + if (message.data[i] < 16) + Serial.print(F("0")); + Serial.print(message.data[i], HEX); + Serial.print(F(" ")); + } + Serial.print(F("[")); + Serial.print(size); + Serial.print(F("] ")); +} + +class Radio { + public: + bool alive = false; // true if radio booted up + bool reported_no_radio = false; + TubeId tubeId = 0; + TubeId masterTubeId = 0; + + unsigned long radioFailures = 0; + unsigned long radioRestarts = 0; + + void setup(bool isMaster) { + if (isMaster) + this->resetId(254); + else + this->resetId(); + +#ifdef USERADIO +#ifdef IS_TEENSY + SPI.setSCK(PIN_RADIO_SCK); + SPI.setMOSI(PIN_RADIO_MOSI); + SPI.setMISO(PIN_RADIO_MISO); +#endif + SPI.begin(); + + this->reported_no_radio = false; + if (_radio.init(RADIO_RX_ID, PIN_RADIO_CE, PIN_RADIO_CSN, RADIO_BITRATE, RADIO_CHANNEL)) { + this->alive = true; + } + Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); + + // Start the radio, but mute & listen for a bit +#endif + } + + void resetId(uint8_t id=0) { + if (id == 0) + id = newTubeId(); + this->tubeId = id; + Serial.print(F("My ID is ")); + Serial.println(this->tubeId); + + if (this->tubeId > this->masterTubeId) + this->masterTubeId = 0; + } + + bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + { + return this->sendCommandFrom(this->tubeId, command, data, size, relayId); + } + + bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + { +#ifndef USERADIO + return true; +#endif + + bool sent = 0; + if (!this->alive) + return sent; + +#ifdef USERADIO + RadioMessage message; + if (size > sizeof(message.data)) { + Serial.println(F("Too big to send")); + return 0; + } + + message.tubeId = id; + message.relayId = relayId; + message.command = command + (RADIO_VERSION << 12); + memset(message.data, 0, sizeof(message.data)); + memcpy(message.data, data, size); + uint16_t crc = calculate_crc(message.data, sizeof(message.data)); + message.crc = crc; + + Serial.print(F("[")); + Serial.print(message.tubeId); + Serial.print(F(": ")); + Serial.print(message.command, HEX); + + sent = _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); + Serial.print(sent ? F(" ok] ") : F(" failed] ")); +#endif + + return sent; + } + + void receiveCommands(MessageReceiver *receiver) + { +#ifdef USERADIO + RadioMessage message; + + if (!this->alive && !this->reported_no_radio) + { + Serial.println(F("No radio")); + this->reported_no_radio = true; + return; + } + + // check for incoming data + while (_radio.hasData()) + { + _radio.readData(&message); + + // Messages must be from a tube with the current version + if ((message.command>>12) != RADIO_VERSION) + return; + + // Ignore relayed messages if we already have a master + if (message.relayId && message.relayId <= this->masterTubeId) + return; + + // Filter out corrupt messages + unsigned long crc = calculate_crc(message.data, sizeof(message.data)); + if (crc != message.crc) { + // Corrupt packet... ignore it. + Serial.print(F("Invalid CRC: ")); + Serial.print(message.crc); + Serial.print(F(" should be ")); + Serial.println(crc); + continue; + } + + // If we detect an ID collision, fix it by choosing a new random one + while (message.tubeId == this->tubeId) { + Serial.print(F("ID collision!")); + this->resetId(); + } + + // Ignore messages from a lower ID + if (message.tubeId < this->tubeId) { + // Don't need to be noisy about relayed messages + if (message.relayId == 0) { + Serial.print(F("Ignoring message from ")); + Serial.println(message.tubeId); + } + return; + } + + if (message.tubeId != 255 && message.tubeId > this->masterTubeId) { + // Found a new master! + this->masterTubeId = message.tubeId; + Serial.print(F("All hail new master ")); + Serial.println(this->masterTubeId); + } + + // Process the command + receiver->onCommand(message.tubeId, message.command & 0xFFF, message.data); + + // Occcasionally relay commands - more frequently if higher ID + uint8_t r = random8(); + if ((r % 3 == 0) && r < this->tubeId) { + Serial.print(F(" (relaying as ")); + Serial.print(this->tubeId); + Serial.print(F(")")); + message.relayId = message.tubeId; + message.tubeId = this->tubeId; + _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); + } + + Serial.println(); + } +#endif + } + +}; + +#endif diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 177f64c14f..dfbf76e9d2 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -168,22 +168,9 @@ class VirtualStrip { brightness = scale8(this->brightness, brightness); for (unsigned i=0; i < num_leds; i++) { -#ifdef DOUBLED - uint8_t pos = (2*i + 1) % this->num_leds; // slope of line is fixed right now at 2:1 -#else uint8_t pos = i; -#endif - CRGB c = this->leds[pos]; -#ifdef DOUBLED - CRGB c1 = this->leds[pos-1]; - CRGB c2 = this->leds[pos+1]; - nblend(c1, c, 128); - nblend(c, c2, 128); - nblend(c, c1, 128); // C is now a weighted average of the three virtual pixels -#endif - nscale8x3(c.r, c.g, c.b, brightness); nscale8x3(c.r, c.g, c.b, this->fader>>8); if (overwrite) From 65d6ff2a57cff90925d24789dc24d7ed9f64273e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 13 Jul 2022 00:56:32 -0700 Subject: [PATCH 029/263] Re-enable particles; fix misc issues --- usermods/Tubes/Tubes.h | 34 ++++++++++++++++--------------- usermods/Tubes/controller.h | 8 ++++---- usermods/Tubes/effects.h | 7 +++---- usermods/Tubes/particle.h | 37 +++++++++++++++++++++------------- usermods/Tubes/radio.h | 2 +- usermods/Tubes/virtual_strip.h | 3 ++- 6 files changed, 51 insertions(+), 40 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index cb167cedd0..ccc87488b1 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -30,15 +30,15 @@ class TubesUsermod : public Usermod { DebugController debug = DebugController(&controller); int* master = NULL; /* master.h deleted */ - void randomize(long seed) { - for (int i = 0; i < seed % 16; i++) { - randomSeed(random(INT_MAX)); - } - random16_add_entropy( random(INT_MAX) ); + void randomize() { + randomSeed(esp_random()); + random16_add_entropy(esp_random()); } public: void setup() { + randomize(); + // Start timing globalTimer.setup(); beats.setup(); @@ -48,8 +48,8 @@ class TubesUsermod : public Usermod { void loop() { - EVERY_N_MILLISECONDS(1000) { - randomize(random(INT_MAX)); + EVERY_N_MILLISECONDS(10000) { + randomize(); } beats.update(); @@ -71,26 +71,28 @@ Aurora Dynamic Smooth Blends Colortwinkles -Fillnoise - maybe Fireworks Fireworks Starburst Flow -Gradient - maybe -Juggle - maybe Lake -Meteor Smooth - maybe Noise 2 Noise 4 Pacifica -Palette - maybe -Phased - maybe Plasma Ripple Running Dual -Saw - maybe -Sinelon Dual - maybe -Tetrix - maybe Twinklecat Twinkleup +MAYBE GOOD PATTERNS +Fillnoise +Gradient +Juggle +Meteor Smooth +Palette +Phased +Saw +Sinelon Dual +Tetrix + */ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 01539dc63c..3856c0e373 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -8,7 +8,7 @@ #include "global_state.h" #include "radio.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 144; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 255; const static CommandId COMMAND_UPDATE = 0x411; const static CommandId COMMAND_NEXT = 0x321; @@ -327,7 +327,9 @@ class PatternController : public MessageReceiver { } uint16_t set_next_effect(uint16_t phrase) { - EffectDef def = gEffects[random8(gEffectCount)]; + uint8_t effect_num = random8(gEffectCount); + + EffectDef def = gEffects[effect_num]; if (def.control.energy > this->energy) def = gEffects[0]; @@ -643,10 +645,8 @@ class PatternController : public MessageReceiver { return; case 'g': - /* PARTICLES for (int i=0; i< 10; i++) addGlitter(); - */ break; case '?': diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index f4ad7532c2..245510aa75 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -100,21 +100,20 @@ class Effects { } void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { - unsigned int len = 0; /* PARTICLES particles.length(); */ + unsigned int len = numParticles; for (unsigned i=len; i > 0; i--) { Particle *particle = particles[i-1]; particle->update(frame); if (particle->age > particle->lifetime) { - delete particle; - /* PARTICLES particles.erase(i-1); */ + removeParticle(i-1); continue; } } } void draw(CRGB strip[], uint8_t num_leds) { - uint8_t len = 0; /* PARTICLES particles.length(); */ + uint8_t len = numParticles; for (uint8_t i=0; idrawFn(particle, strip, num_leds); diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d49f10a1bb..d31e4185d7 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -3,9 +3,8 @@ #include "wled.h" #include "beats.h" #include "options.h" -// #include "ustd.h" -#define MAX_PARTICLES 20 +#define MAX_PARTICLES 80 #undef PARTICLE_PALETTES class Particle; @@ -154,25 +153,35 @@ class Particle { }; -Particle* particles[5]; -/* PARTICLES -ustd::array particles = ustd::array(5); -*/ +Particle* particles[MAX_PARTICLES]; BeatFrame_24_8 particle_beat_frame; +uint8_t numParticles = 0; + +void removeParticle(uint8_t i) { + if (i >= numParticles) + return; + + // Free the memory of the old particle + Particle *old_particle = particles[i]; + delete old_particle; + + // Reset the current free particle + int rest = numParticles - i; + if (rest > 0) { + memmove(&particles[i], &particles[i+1], sizeof(particles[0]) * rest); + } + + numParticles -= 1; +} void addParticle(Particle *particle) { particle->born = particle_beat_frame; - /* PARTICLES - particles.add(particle); - if (particles.length() > MAX_PARTICLES) { - Particle *old_particle = particles[0]; - delete old_particle; - particles.erase(0); + if (numParticles >= MAX_PARTICLES) { + removeParticle(0); } - */ + particles[numParticles++] = particle; } - void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 86e3883c0c..53cb3df15a 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -2,11 +2,11 @@ #define RADIO_H #include -#include #define RADIO_VERSION 1 #ifdef USERADIO +#include NRFLite _radio(Serial); #endif diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index dfbf76e9d2..2312724709 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -35,7 +35,8 @@ BeatFrame_24_8 swing(BeatFrame_24_8 frame) { } class VirtualStrip { - const static uint16_t DEF_BRIGHT = 192; + // Let WLED do the dimming + const static uint16_t DEF_BRIGHT = 255; public: CRGB leds[MAX_VIRTUAL_LEDS]; From b98efb691842199ca5fd423fd49e3e8fb112e4cd Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 13 Jul 2022 01:05:37 -0700 Subject: [PATCH 030/263] lower default brightness a bit to emphasize effects --- usermods/Tubes/controller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3856c0e373..f873863a3a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -8,7 +8,7 @@ #include "global_state.h" #include "radio.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 255; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static CommandId COMMAND_UPDATE = 0x411; const static CommandId COMMAND_NEXT = 0x321; From 43554409b35a8332026979d77fd2f934b7179a4e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 18 Jul 2022 00:35:22 -0700 Subject: [PATCH 031/263] Introduce BLE --- usermods/Tubes/controller.h | 25 +++++----- usermods/Tubes/radio.h | 98 +++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 64 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index f873863a3a..fcba001e19 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -10,13 +10,14 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -const static CommandId COMMAND_UPDATE = 0x411; -const static CommandId COMMAND_NEXT = 0x321; -const static CommandId COMMAND_RESET = 0x911; -const static CommandId COMMAND_FIREWORK = 0xFFF; -const static CommandId COMMAND_HELLO = 0x000; -const static CommandId COMMAND_OPTIONS = 0x123; -const static CommandId COMMAND_BRIGHTNESS = 0x888; +const static CommandId COMMAND_HELLO = 0x00; +const static CommandId COMMAND_OPTIONS = 0x10; +const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_NEXT = 0x30; +const static CommandId COMMAND_RESET = 0xF0; + +const static CommandId COMMAND_BRIGHTNESS = 0x80; +const static CommandId COMMAND_FIREWORK = 0x90; typedef struct { @@ -139,9 +140,9 @@ class PatternController : public MessageReceiver { this->read_keys(); // If master has expired, clear masterId - if (this->radio->masterTubeId && this->slaveTimer.ended()) { + if (this->radio->uplinkTubeId && this->slaveTimer.ended()) { Serial.println(F("I have no master")); - this->radio->masterTubeId = 0; + this->radio->uplinkTubeId = 0; } // Update patterns to the beat @@ -162,7 +163,7 @@ class PatternController : public MessageReceiver { } // If alone or master, send out updates - if (!this->radio->masterTubeId and this->updateTimer.ended()) { + if (!this->radio->uplinkTubeId and this->updateTimer.ended()) { this->send_update(); } @@ -462,7 +463,7 @@ class PatternController : public MessageReceiver { case COMMAND_NEXT: { Serial.print(F(" next ")); - if (fromId < this->radio->masterTubeId) { + if (fromId < this->radio->uplinkTubeId) { Serial.print(F(" (ignoring)")); return; } @@ -475,7 +476,7 @@ class PatternController : public MessageReceiver { case COMMAND_UPDATE: { Serial.print(F(" update ")); - if (fromId < this->radio->masterTubeId) { + if (fromId < this->radio->uplinkTubeId) { Serial.print(F(" (ignoring)")); return; } diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 53cb3df15a..d6ae60bec9 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -1,31 +1,25 @@ -#ifndef RADIO_H -#define RADIO_H +#pragma once #include +#include #define RADIO_VERSION 1 +// #define USEBLE -#ifdef USERADIO -#include -NRFLite _radio(Serial); -#endif +#ifdef USEBLE +#include "bluetooth.h" +#include -const static uint8_t RADIO_TX_ID = 0; // Radio ID for both trans -const static uint8_t RADIO_RX_ID = 0; // Radio ID for both recv +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -const static uint8_t PIN_RADIO_CE = 9; // hardware pins -const static uint8_t PIN_RADIO_CSN = 10; // hardware pins -const static uint8_t PIN_RADIO_MOSI = 11; // hardware pins -const static uint8_t PIN_RADIO_MISO = 12; // hardware pins -const static uint8_t PIN_RADIO_SCK = 13; // hardware pins +static NimBLEUUID dataUuid(SERVICE_UUID); +#endif -#define RADIO_BITRATE NRFLite::BITRATE1MBPS // { BITRATE2MBPS, BITRATE1MBPS, BITRATE250KBPS } -#define RADIO_CHANNEL 100 + RADIO_VERSION // Channel hop with each version #define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec class Radio; -typedef uint16_t CommandId; +typedef uint8_t CommandId; typedef uint8_t TubeId; #define MESSAGE_DATA_MAX_SIZE 25 @@ -87,7 +81,8 @@ class Radio { bool alive = false; // true if radio booted up bool reported_no_radio = false; TubeId tubeId = 0; - TubeId masterTubeId = 0; + TubeId uplinkTubeId = 0; + char tube_name[20]; unsigned long radioFailures = 0; unsigned long radioRestarts = 0; @@ -97,23 +92,16 @@ class Radio { this->resetId(254); else this->resetId(); - -#ifdef USERADIO -#ifdef IS_TEENSY - SPI.setSCK(PIN_RADIO_SCK); - SPI.setMOSI(PIN_RADIO_MOSI); - SPI.setMISO(PIN_RADIO_MISO); + +#ifdef USEBLE + ble_setup(); #endif - SPI.begin(); - - this->reported_no_radio = false; - if (_radio.init(RADIO_RX_ID, PIN_RADIO_CE, PIN_RADIO_CSN, RADIO_BITRATE, RADIO_CHANNEL)) { - this->alive = true; - } - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); + Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit -#endif + } + + void update() { } void resetId(uint8_t id=0) { @@ -123,8 +111,18 @@ class Radio { Serial.print(F("My ID is ")); Serial.println(this->tubeId); - if (this->tubeId > this->masterTubeId) - this->masterTubeId = 0; + if (this->tubeId > this->uplinkTubeId) + this->uplinkTubeId = 0; + +#ifdef USEBLE + if (this->alive) + NimBLEDevice::deinit(false); + + sprintf(tube_name, "Tube %02X", this->tubeId); + NimBLEDevice::init(std::string(tube_name)); + delay(1000); + this->alive = true; +#endif } bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) @@ -134,24 +132,19 @@ class Radio { bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) { -#ifndef USERADIO - return true; -#endif - - bool sent = 0; + bool sent = false; if (!this->alive) - return sent; + return true; -#ifdef USERADIO RadioMessage message; if (size > sizeof(message.data)) { Serial.println(F("Too big to send")); - return 0; + return false; } message.tubeId = id; message.relayId = relayId; - message.command = command + (RADIO_VERSION << 12); + message.command = command + RADIO_VERSION; memset(message.data, 0, sizeof(message.data)); memcpy(message.data, data, size); uint16_t crc = calculate_crc(message.data, sizeof(message.data)); @@ -161,11 +154,14 @@ class Radio { Serial.print(message.tubeId); Serial.print(F(": ")); Serial.print(message.command, HEX); - - sent = _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); - Serial.print(sent ? F(" ok] ") : F(" failed] ")); + +#ifdef USEBLE + sent = ble_broadcast((byte *)&message, sizeof(message)); +#else + sent = true; #endif + Serial.print(sent ? F(" ok] ") : F(" failed] ")); return sent; } @@ -191,7 +187,7 @@ class Radio { return; // Ignore relayed messages if we already have a master - if (message.relayId && message.relayId <= this->masterTubeId) + if (message.relayId && message.relayId <= this->uplinkTubeId) return; // Filter out corrupt messages @@ -221,11 +217,11 @@ class Radio { return; } - if (message.tubeId != 255 && message.tubeId > this->masterTubeId) { + if (message.tubeId != 255 && message.tubeId > this->uplinkTubeId) { // Found a new master! - this->masterTubeId = message.tubeId; - Serial.print(F("All hail new master ")); - Serial.println(this->masterTubeId); + this->uplinkTubeId = message.tubeId; + Serial.print(F("My new uplink is ")); + Serial.println(this->uplinkTubeId); } // Process the command @@ -248,5 +244,3 @@ class Radio { } }; - -#endif From 00820a52b41eca62a90bd1957be0fcc4f7e5817f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 18 Jul 2022 00:35:46 -0700 Subject: [PATCH 032/263] Introduce BLE --- usermods/Tubes/Tubes.h | 1 + usermods/Tubes/bluetooth.h | 217 +++++++++++++++++++++++++++++++++++++ usermods/Tubes/debug.h | 8 +- 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 usermods/Tubes/bluetooth.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index ccc87488b1..d82375b659 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -55,6 +55,7 @@ class TubesUsermod : public Usermod { beats.update(); controller.update(); debug.update(); + radio.update(); // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h new file mode 100644 index 0000000000..fa2e68c5dd --- /dev/null +++ b/usermods/Tubes/bluetooth.h @@ -0,0 +1,217 @@ +#pragma once + +#include +#include + +static NimBLEServer* pServer; + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer) { + Serial.println("Client connected"); + Serial.println("Multi-connect support: start advertising"); + NimBLEDevice::startAdvertising(); + }; + + /** Alternative onConnect() method to extract details of the connection. + * See: src/ble_gap.h for the details of the ble_gap_conn_desc struct. + */ + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + Serial.print("Client address: "); + Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + /** We can use the connection handle here to ask for different connection parameters. + * Args: connection handle, min connection interval, max connection interval + * latency, supervision timeout. + * Units; Min/Max Intervals: 1.25 millisecond increments. + * Latency: number of intervals allowed to skip. + * Timeout: 10 millisecond increments, try for 5x interval time for best results. + */ + pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60); + }; + + void onDisconnect(NimBLEServer* pServer) { + Serial.println("Client disconnected - start advertising"); + NimBLEDevice::startAdvertising(); + }; + + void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) { + Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); + }; + +/********************* Security handled here ********************** +****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + Serial.println("Server Passkey Request"); + /** This should return a random 6 digit number for security + * or make your own static passkey as done here. + */ + return 123456; + }; + + bool onConfirmPIN(uint32_t pass_key){ + Serial.print("The passkey YES/NO number: ");Serial.println(pass_key); + /** Return false if passkeys don't match. */ + return true; + }; + + void onAuthenticationComplete(ble_gap_conn_desc* desc){ + /** Check that encryption was successful, if not we disconnect the client */ + if(!desc->sec_state.encrypted) { + NimBLEDevice::getServer()->disconnect(desc->conn_handle); + Serial.println("Encrypt connection failed - disconnecting client"); + return; + } + Serial.println("Starting BLE work!"); + }; +}; + +/** Handler class for characteristic actions */ +class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { + void onRead(NimBLECharacteristic* pCharacteristic){ + Serial.print(pCharacteristic->getUUID().toString().c_str()); + Serial.print(": onRead(), value: "); + Serial.println(pCharacteristic->getValue().c_str()); + }; + + void onWrite(NimBLECharacteristic* pCharacteristic) { + Serial.print(pCharacteristic->getUUID().toString().c_str()); + Serial.print(": onWrite(), value: "); + Serial.println(pCharacteristic->getValue().c_str()); + }; + + /** Called before notification or indication is sent, + * the value can be changed here before sending if desired. + */ + void onNotify(NimBLECharacteristic* pCharacteristic) { + Serial.println("Sending notification to clients"); + }; + + + /** The status returned in status is defined in NimBLECharacteristic.h. + * The value returned in code is the NimBLE host return code. + */ + void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) { + String str = ("Notification/Indication status code: "); + str += status; + str += ", return code: "; + str += code; + str += ", "; + str += NimBLEUtils::returnCodeToString(code); + Serial.println(str); + }; + + void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) { + String str = "Client ID: "; + str += desc->conn_handle; + str += " Address: "; + str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str(); + if(subValue == 0) { + str += " Unsubscribed to "; + }else if(subValue == 1) { + str += " Subscribed to notfications for "; + } else if(subValue == 2) { + str += " Subscribed to indications for "; + } else if(subValue == 3) { + str += " Subscribed to notifications and indications for "; + } + str += std::string(pCharacteristic->getUUID()).c_str(); + + Serial.println(str); + }; +}; + +/** Handler class for descriptor actions */ +class DescriptorCallbacks : public NimBLEDescriptorCallbacks { + void onWrite(NimBLEDescriptor* pDescriptor) { + std::string dscVal = pDescriptor->getValue(); + Serial.print("Descriptor witten value:"); + Serial.println(dscVal.c_str()); + }; + + void onRead(NimBLEDescriptor* pDescriptor) { + Serial.print(pDescriptor->getUUID().toString().c_str()); + Serial.println(" Descriptor read"); + }; +}; + + +/** Define callback instances globally to use for multiple Charateristics \ Descriptors */ +static DescriptorCallbacks dscCallbacks; +static CharacteristicCallbacks chrCallbacks; + + +void ble_setup() { + esp_coex_preference_set(ESP_COEX_PREFER_BT); + return; + +// /** Optional: set the transmit power, default is 3db */ +// #ifdef ESP_PLATFORM +// NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ +// #else +// NimBLEDevice::setPower(9); /** +9db */ +// #endif + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); + NimBLEDevice::setSecurityAuth(false, false, true); + + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks()); + + NimBLEService* pTubeService = pServer->createService("D00B"); + NimBLECharacteristic* pFoodCharacteristic = pTubeService->createCharacteristic( + "FEED", + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + ); + + pFoodCharacteristic->setValue("Fries"); + pFoodCharacteristic->setCallbacks(&chrCallbacks); + + /** Start the services when finished creating all Characteristics and Descriptors */ + pTubeService->start(); + + /** Add the services to the advertisment data **/ + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(pTubeService->getUUID()); + pAdvertising->setScanResponse(false); + pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); + // pAdvertising->start(); + + Serial.println("Advertising Started"); + delay(1000); +} + + +bool ble_broadcast(byte *data, int size) { + return true; + + // Broadcast the current effect state to every connected client + if (pServer->getConnectedCount() == 0) + return true; + + NimBLEService* pTubeService = pServer->getServiceByUUID("D00B"); + if (!pTubeService) + return true; + + NimBLECharacteristic* pFoodCharacteristic = pTubeService->getCharacteristic("FEED"); + if(!pFoodCharacteristic) + return true; + + // Update the characteristic + byte buffer[40]; + if (size > 40) { + Serial.println("Too much data"); + return false; + } + memcpy(buffer, data, size); + + pFoodCharacteristic->setValue(buffer); + pFoodCharacteristic->notify(true); + + return true; +} \ No newline at end of file diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 8f4717ab89..6134fab0db 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -27,6 +27,12 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { + Serial.printf("IP: %u.%u.%u.%u ", + WiFi.localIP()[0], + WiFi.localIP()[1], + WiFi.localIP()[2], + WiFi.localIP()[3] + ); Serial.print(F("Free memory: ")); Serial.println( freeMemory() ); } @@ -40,7 +46,7 @@ class DebugController { uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->masterTubeId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->radio->uplinkTubeId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { From a8183d7435f5de3a73f6b366f3b5659a65f7f8d0 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 21 Jul 2022 00:55:29 -0700 Subject: [PATCH 033/263] More BLE investigation --- usermods/Tubes/bluetooth.h | 34 ++++++++++++++++++++++++++-------- usermods/Tubes/radio.h | 22 +--------------------- wled00/wled.h | 1 + 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index fa2e68c5dd..21c8e66e14 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -3,8 +3,11 @@ #include #include +#define USEBLE static NimBLEServer* pServer; +bool initialized = false; + /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ class ServerCallbacks: public NimBLEServerCallbacks { @@ -141,16 +144,29 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; +void ble_init(char *tube_name) { +#ifndef USEBLE + return; +#endif + if (initialized) + NimBLEDevice::deinit(false); + + NimBLEDevice::init(std::string(tube_name)); + initialized = true; +} + void ble_setup() { - esp_coex_preference_set(ESP_COEX_PREFER_BT); +#ifndef USEBLE return; +#endif + esp_coex_preference_set(ESP_COEX_PREFER_BT); -// /** Optional: set the transmit power, default is 3db */ -// #ifdef ESP_PLATFORM -// NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ -// #else -// NimBLEDevice::setPower(9); /** +9db */ -// #endif + /** Optional: set the transmit power, default is 3db */ +#ifdef ESP_PLATFORM + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ +#else + NimBLEDevice::setPower(9); /** +9db */ +#endif /** Set the IO capabilities of the device, each option will trigger a different pairing method. * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing @@ -188,8 +204,10 @@ void ble_setup() { bool ble_broadcast(byte *data, int size) { +#ifndef USEBLE return true; - +#endif + // Broadcast the current effect state to every connected client if (pServer->getConnectedCount() == 0) return true; diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index d6ae60bec9..aa826ded72 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -4,16 +4,8 @@ #include #define RADIO_VERSION 1 -// #define USEBLE -#ifdef USEBLE #include "bluetooth.h" -#include - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" - -static NimBLEUUID dataUuid(SERVICE_UUID); -#endif #define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec @@ -93,9 +85,7 @@ class Radio { else this->resetId(); -#ifdef USEBLE ble_setup(); -#endif Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit @@ -114,15 +104,9 @@ class Radio { if (this->tubeId > this->uplinkTubeId) this->uplinkTubeId = 0; -#ifdef USEBLE - if (this->alive) - NimBLEDevice::deinit(false); - sprintf(tube_name, "Tube %02X", this->tubeId); - NimBLEDevice::init(std::string(tube_name)); - delay(1000); + ble_init(tube_name); this->alive = true; -#endif } bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) @@ -155,11 +139,7 @@ class Radio { Serial.print(F(": ")); Serial.print(message.command, HEX); -#ifdef USEBLE sent = ble_broadcast((byte *)&message, sizeof(message)); -#else - sent = true; -#endif Serial.print(sent ? F(" ok] ") : F(" failed] ")); return sent; diff --git a/wled00/wled.h b/wled00/wled.h index 706d8eaa92..7c43429d0b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -16,6 +16,7 @@ #define WLED_DISABLE_LOXONE #define WLED_DISABLE_ALEXA #define WLED_DISABLE_INFRARED +#define WLED_DISABLE_CRONIXIE // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From 39ebe3999467d17ed4892bf46f14fc7a9c6e72f5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 21 Jul 2022 03:02:54 -0700 Subject: [PATCH 034/263] Fix BLE initialization --- usermods/Tubes/bluetooth.h | 43 +++++++++++++++++--------------------- usermods/Tubes/radio.h | 9 +++++--- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 21c8e66e14..95d17607d3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -6,7 +6,7 @@ #define USEBLE static NimBLEServer* pServer; -bool initialized = false; +bool bluetooth_on = false; /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ @@ -144,23 +144,7 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; -void ble_init(char *tube_name) { -#ifndef USEBLE - return; -#endif - if (initialized) - NimBLEDevice::deinit(false); - - NimBLEDevice::init(std::string(tube_name)); - initialized = true; -} - void ble_setup() { -#ifndef USEBLE - return; -#endif - esp_coex_preference_set(ESP_COEX_PREFER_BT); - /** Optional: set the transmit power, default is 3db */ #ifdef ESP_PLATFORM NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ @@ -196,17 +180,14 @@ void ble_setup() { pAdvertising->addServiceUUID(pTubeService->getUUID()); pAdvertising->setScanResponse(false); pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - // pAdvertising->start(); + pAdvertising->start(); Serial.println("Advertising Started"); - delay(1000); } - bool ble_broadcast(byte *data, int size) { -#ifndef USEBLE - return true; -#endif + if (!bluetooth_on) + return true; // Broadcast the current effect state to every connected client if (pServer->getConnectedCount() == 0) @@ -232,4 +213,18 @@ bool ble_broadcast(byte *data, int size) { pFoodCharacteristic->notify(true); return true; -} \ No newline at end of file +} + +void ble_init(char *name) { + if (bluetooth_on) { + NimBLEDevice::deinit(false); + bluetooth_on = false; + } + + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + esp_coex_preference_set(ESP_COEX_PREFER_BT); + NimBLEDevice::init(std::string(name)); + ble_setup(); + bluetooth_on = true; +} + diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index aa826ded72..1dff0a82d2 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -85,13 +85,15 @@ class Radio { else this->resetId(); - ble_setup(); - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit } void update() { + if (millis() > 500 && !bluetooth_on) { + Serial.println("INITIALIZE BLUETOOTH"); + ble_init(tube_name); + } } void resetId(uint8_t id=0) { @@ -105,7 +107,8 @@ class Radio { this->uplinkTubeId = 0; sprintf(tube_name, "Tube %02X", this->tubeId); - ble_init(tube_name); + if (bluetooth_on) + ble_init(tube_name); this->alive = true; } From 0d7865aabe12bac778a0f19d45e51cd9f31afdba Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 22 Jul 2022 05:03:36 -0700 Subject: [PATCH 035/263] New bluetooth mesh code --- usermods/Tubes/bluetooth.h | 282 ++++++++++++++++++++++++++---------- usermods/Tubes/controller.h | 40 +---- usermods/Tubes/debug.h | 4 +- usermods/Tubes/radio.h | 57 ++------ 4 files changed, 231 insertions(+), 152 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 95d17607d3..52217e1315 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -3,10 +3,16 @@ #include #include -#define USEBLE -static NimBLEServer* pServer; -bool bluetooth_on = false; +typedef uint16_t MeshId; + +typedef struct { + MeshId id = 0; + MeshId uplinkId = 0; +} MeshIds; + + +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ @@ -138,93 +144,223 @@ class DescriptorCallbacks : public NimBLEDescriptorCallbacks { }; }; - /** Define callback instances globally to use for multiple Charateristics \ Descriptors */ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; -void ble_setup() { - /** Optional: set the transmit power, default is 3db */ -#ifdef ESP_PLATFORM - NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ -#else - NimBLEDevice::setPower(9); /** +9db */ -#endif +class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { + public: + bool alive = false; // true if radio booted up - /** Set the IO capabilities of the device, each option will trigger a different pairing method. - * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing - * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing - * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing - */ - NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); - NimBLEDevice::setSecurityAuth(false, false, true); + MeshIds ids; + byte buffer[100]; + char node_name[20]; - pServer = NimBLEDevice::createServer(); - pServer->setCallbacks(new ServerCallbacks()); + uint16_t serviceUUID = 0xD00F; - NimBLEService* pTubeService = pServer->createService("D00B"); - NimBLECharacteristic* pFoodCharacteristic = pTubeService->createCharacteristic( - "FEED", - NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST - ); + NimBLEServer* pServer = NULL; + NimBLEService* pService = NULL; + NimBLEScan* pScanner = NULL; - pFoodCharacteristic->setValue("Fries"); - pFoodCharacteristic->setCallbacks(&chrCallbacks); + Timer uplinkTimer; - /** Start the services when finished creating all Characteristics and Descriptors */ - pTubeService->start(); + MeshId newMeshId() { + return random(0, 4000); + } - /** Add the services to the advertisment data **/ - NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); - pAdvertising->addServiceUUID(pTubeService->getUUID()); - pAdvertising->setScanResponse(false); - pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - pAdvertising->start(); + void advertise() { + if (!pService) + return; - Serial.println("Advertising Started"); -} + // Add the services to the advertisement data + auto pAdvertising = NimBLEDevice::getAdvertising(); + if (!pAdvertising) + return; -bool ble_broadcast(byte *data, int size) { - if (!bluetooth_on) - return true; + auto service_data = std::string((char *)&ids, sizeof(ids)); + if (ids.uplinkId) + sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); + else + sprintf(node_name, "Tube %03X", ids.id); - // Broadcast the current effect state to every connected client - if (pServer->getConnectedCount() == 0) - return true; + // // // Reset the device name + // NimBLEDevice::deinit(false); + // NimBLEDevice::init(node_name); - NimBLEService* pTubeService = pServer->getServiceByUUID("D00B"); - if (!pTubeService) - return true; + // Set advertisement + pAdvertising->stop(); + pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); + pAdvertising->start(); - NimBLECharacteristic* pFoodCharacteristic = pTubeService->getCharacteristic("FEED"); - if(!pFoodCharacteristic) - return true; - - // Update the characteristic - byte buffer[40]; - if (size > 40) { - Serial.println("Too much data"); - return false; + Serial.printf("Advertising %s", node_name); + Serial.println(); + } + + void init_service() { + /** Optional: set the transmit power, default is 3db */ + #ifdef ESP_PLATFORM + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ + #else + NimBLEDevice::setPower(9); /** +9db */ + #endif + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); + NimBLEDevice::setSecurityAuth(false, false, true); + + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks()); + + pService = pServer->createService("D00B"); + NimBLECharacteristic* pCharacteristic = pService->createCharacteristic( + "FEED", + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + ); + + pCharacteristic->setValue("Test"); + pCharacteristic->setCallbacks(&chrCallbacks); + + /** Start the services when finished creating all Characteristics and Descriptors */ + pService->start(); + + /** Add the services to the advertisement data **/ + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(pService->getUUID()); + pAdvertising->setScanResponse(false); + pAdvertising->setAppearance(0x07C6); // Multi-color LED array + pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); + + advertise(); } - memcpy(buffer, data, size); - - pFoodCharacteristic->setValue(buffer); - pFoodCharacteristic->notify(true); - - return true; -} - -void ble_init(char *name) { - if (bluetooth_on) { - NimBLEDevice::deinit(false); - bluetooth_on = false; + + void init_scanner() { + NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); + NimBLEDevice::setScanDuplicateCacheSize(200); + + pScanner = NimBLEDevice::getScan(); //create new scan + // Set the callback for when devices are discovered, no duplicates. + pScanner->setAdvertisedDeviceCallbacks(this, false); + pScanner->setActiveScan(false); // Don't request data (it uses more energy) + pScanner->setInterval(200); // How often the scan occurs / switches channels; in milliseconds, + pScanner->setWindow(80); // How long to scan during the interval; in milliseconds. + pScanner->setMaxResults(0); // do not store the scan results, use callback only. + } + + void init() { + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + esp_coex_preference_set(ESP_COEX_PREFER_BT); + NimBLEDevice::init(std::string("Tube")); + init_scanner(); + init_service(); + this->alive = true; + } + + void uplink_ping() { + // Track the last time we received a message from our master + this->uplinkTimer.start(UPLINK_TIMEOUT); } - esp_wifi_set_ps(WIFI_PS_MIN_MODEM); - esp_coex_preference_set(ESP_COEX_PREFER_BT); - NimBLEDevice::init(std::string(name)); - ble_setup(); - bluetooth_on = true; -} + void update() { + // Don't do anything for the first half-second to avoid crashing WiFi + if (millis() < 500) + return; + + if (!this->alive) { + this->init(); + } + + // Check the last time we heard from the uplink node + if (is_following() && this->uplinkTimer.ended()) { + Serial.println("Uplink lost"); + follow(0); + } + + if (!this->pScanner->isScanning()) { + // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. + this->pScanner->start(0, nullptr, false); + } + } + void broadcast(byte *data, int size) { + if (size > sizeof(buffer)) { + Serial.println("Too much data"); + return; + } + + memset(buffer, 0, sizeof(buffer)); + memcpy(buffer, data, size); + + if (!pServer) + return; + + // Broadcast the current effect state to every connected client + if (pServer->getConnectedCount() == 0) + return; + + if (!pService) + return; + + NimBLECharacteristic* pCharacteristic = pService->getCharacteristic("FEED"); + if(!pCharacteristic) + return; + + // Update the characteristic + pCharacteristic->setValue(buffer); + pCharacteristic->notify(true); + } + + void reset(MeshId id = 0) { + if (id == 0) + id = newMeshId(); + this->ids.id = id; + + Serial.printf("My ID is %03X", this->ids.id); + if (this->ids.id > this->ids.uplinkId) + this->ids.uplinkId = 0; + + advertise(); + } + + void follow(MeshId uplinkId) { + if (this->ids.uplinkId == uplinkId) + return; + + this->ids.uplinkId = uplinkId; + advertise(); + } + + bool is_following() { + return this->ids.uplinkId != 0; + } + + // ====== CALLBACKS ======= + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { + if (!advertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) + return; + + // Make sure it's booted up and advertising data + auto data = advertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); + if (!data.length()) + return; + + MeshIds data_ids; + memcpy(&data_ids, data.c_str(), data.length()); + Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); + + if (data_ids.id >= ids.uplinkId) { + follow(data_ids.id); + uplink_ping(); + } + + Serial.printf("Found: %s: %s\n", + std::string(advertisedDevice->getAddress()).c_str(), + std::string(advertisedDevice->getName()).c_str() + ); + } + +}; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index fcba001e19..8e0c2c435b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -74,7 +74,6 @@ class PatternController : public MessageReceiver { Timer graphicsTimer; Timer updateTimer; - Timer slaveTimer; #ifdef USELCD Lcd *lcd; @@ -131,7 +130,6 @@ class PatternController : public MessageReceiver { this->radio->setup(this->isMaster); this->radio->sendCommand(COMMAND_HELLO); - this->slaveTimer.start(RADIO_SENDPERIOD * 3); // Assume we're a slave at first, just listen for a master. this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to } @@ -139,12 +137,6 @@ class PatternController : public MessageReceiver { { this->read_keys(); - // If master has expired, clear masterId - if (this->radio->uplinkTubeId && this->slaveTimer.ended()) { - Serial.println(F("I have no master")); - this->radio->uplinkTubeId = 0; - } - // Update patterns to the beat this->update_beat(); @@ -162,8 +154,8 @@ class PatternController : public MessageReceiver { this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); } - // If alone or master, send out updates - if (!this->radio->uplinkTubeId and this->updateTimer.ended()) { + // Update current status + if (this->updateTimer.ended()) { this->send_update(); } @@ -220,19 +212,8 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - if (this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state))) { - this->radio->radioFailures = 0; - this->updateTimer.snooze(RADIO_SENDPERIOD); - } else { - // might have been a collision. Back off by a small amount determined by ID - Serial.println(F("Radio update failed")); - this->updateTimer.snooze( this->radio->tubeId & 0x7F ); - this->radio->radioFailures++; - if (this->radio->radioFailures > 100) { - this->radio->setup(this->isMaster); - this->radio->radioRestarts++; - } - } + this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state)); + this->updateTimer.snooze(RADIO_SENDPERIOD); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -463,10 +444,6 @@ class PatternController : public MessageReceiver { case COMMAND_NEXT: { Serial.print(F(" next ")); - if (fromId < this->radio->uplinkTubeId) { - Serial.print(F(" (ignoring)")); - return; - } memcpy(&this->next_state, data, sizeof(TubeState)); this->next_state.print(); @@ -476,19 +453,12 @@ class PatternController : public MessageReceiver { case COMMAND_UPDATE: { Serial.print(F(" update ")); - if (fromId < this->radio->uplinkTubeId) { - Serial.print(F(" (ignoring)")); - return; - } TubeState state; memcpy(&state, data, sizeof(TubeState)); state.print(); Serial.print(F(" (obeying)")); - // Track the last time we received a message from our master - this->slaveTimer.start(RADIO_SENDPERIOD * 8); - // Catch up to this state this->load_pattern(state); this->load_palette(state); @@ -559,7 +529,7 @@ class PatternController : public MessageReceiver { break; case 'i': - this->radio->resetId(arg >> 8); + this->radio->mesh_node.reset(arg >> 8); break; case 'd': diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 6134fab0db..861ae393bd 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -43,10 +43,10 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->radio->mesh_node.ids.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->uplinkTubeId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->radio->mesh_node.ids.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 1dff0a82d2..3f44c41754 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -12,17 +12,15 @@ class Radio; typedef uint8_t CommandId; -typedef uint8_t TubeId; #define MESSAGE_DATA_MAX_SIZE 25 typedef struct { CommandId command; - TubeId tubeId; - TubeId relayId; byte data[MESSAGE_DATA_MAX_SIZE]; uint16_t crc = 0; } RadioMessage; + class MessageReceiver { public: @@ -50,9 +48,6 @@ uint16_t calculate_crc( byte *data, byte len ) { return crc & 65535; } -uint8_t newTubeId() { - return random(10, 250); // Leave room for master -} void printMessageData(RadioMessage &message, int size) { Serial.print(sizeof(message.data)); @@ -72,52 +67,32 @@ class Radio { public: bool alive = false; // true if radio booted up bool reported_no_radio = false; - TubeId tubeId = 0; - TubeId uplinkTubeId = 0; - char tube_name[20]; + + BLEMeshNode mesh_node = BLEMeshNode(); unsigned long radioFailures = 0; unsigned long radioRestarts = 0; void setup(bool isMaster) { if (isMaster) - this->resetId(254); + mesh_node.reset(4500); else - this->resetId(); + mesh_node.reset(); Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit } void update() { - if (millis() > 500 && !bluetooth_on) { - Serial.println("INITIALIZE BLUETOOTH"); - ble_init(tube_name); - } - } - - void resetId(uint8_t id=0) { - if (id == 0) - id = newTubeId(); - this->tubeId = id; - Serial.print(F("My ID is ")); - Serial.println(this->tubeId); - - if (this->tubeId > this->uplinkTubeId) - this->uplinkTubeId = 0; - - sprintf(tube_name, "Tube %02X", this->tubeId); - if (bluetooth_on) - ble_init(tube_name); - this->alive = true; + mesh_node.update(); } - bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) { - return this->sendCommandFrom(this->tubeId, command, data, size, relayId); + return this->sendCommandFrom(0, command, data, size, relayId); } - bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + bool sendCommandFrom(MeshId id, uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) { bool sent = false; if (!this->alive) @@ -129,8 +104,6 @@ class Radio { return false; } - message.tubeId = id; - message.relayId = relayId; message.command = command + RADIO_VERSION; memset(message.data, 0, sizeof(message.data)); memcpy(message.data, data, size); @@ -138,14 +111,14 @@ class Radio { message.crc = crc; Serial.print(F("[")); - Serial.print(message.tubeId); + Serial.print(mesh_node.ids.id); Serial.print(F(": ")); Serial.print(message.command, HEX); - sent = ble_broadcast((byte *)&message, sizeof(message)); + mesh_node.broadcast((byte *)&message, sizeof(message)); Serial.print(sent ? F(" ok] ") : F(" failed] ")); - return sent; + return true; } void receiveCommands(MessageReceiver *receiver) @@ -170,7 +143,7 @@ class Radio { return; // Ignore relayed messages if we already have a master - if (message.relayId && message.relayId <= this->uplinkTubeId) + if (message.uplinkId && message.uplinkId <= this->uplinkTubeId) return; // Filter out corrupt messages @@ -193,7 +166,7 @@ class Radio { // Ignore messages from a lower ID if (message.tubeId < this->tubeId) { // Don't need to be noisy about relayed messages - if (message.relayId == 0) { + if (message.uplinkId == 0) { Serial.print(F("Ignoring message from ")); Serial.println(message.tubeId); } @@ -216,7 +189,7 @@ class Radio { Serial.print(F(" (relaying as ")); Serial.print(this->tubeId); Serial.print(F(")")); - message.relayId = message.tubeId; + message.reluplinkIdayId = message.tubeId; message.tubeId = this->tubeId; _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); } From 63c8ec7ffc6f05f488ba59c7f7a7f284f89f8c98 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 22 Jul 2022 05:19:24 -0700 Subject: [PATCH 036/263] Fix uplinkId following --- usermods/Tubes/bluetooth.h | 39 +++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 52217e1315..1784bc57a3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -152,6 +152,7 @@ static CharacteristicCallbacks chrCallbacks; class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { public: bool alive = false; // true if radio booted up + bool changed = false; MeshIds ids; byte buffer[100]; @@ -179,10 +180,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { return; auto service_data = std::string((char *)&ids, sizeof(ids)); - if (ids.uplinkId) - sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); - else - sprintf(node_name, "Tube %03X", ids.id); + sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); // // // Reset the device name // NimBLEDevice::deinit(false); @@ -235,7 +233,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->setAppearance(0x07C6); // Multi-color LED array pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - advertise(); + changed = true; } void init_scanner() { @@ -280,6 +278,11 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { follow(0); } + if (changed) { + advertise(); + changed = false; + } + if (!this->pScanner->isScanning()) { // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. this->pScanner->start(0, nullptr, false); @@ -317,25 +320,31 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void reset(MeshId id = 0) { if (id == 0) id = newMeshId(); - this->ids.id = id; - - Serial.printf("My ID is %03X", this->ids.id); - if (this->ids.id > this->ids.uplinkId) - this->ids.uplinkId = 0; + Serial.printf("My new ID is %03X", id); - advertise(); + this->ids.id = id; + MeshId uplinkId = this->ids.uplinkId; + if (id > uplinkId) + uplinkId = id; + follow(uplinkId); + changed = true; } void follow(MeshId uplinkId) { + if (uplinkId == 0) { + // Following zero means "follow yourself" + uplinkId = this->ids.id; + } + if (this->ids.uplinkId == uplinkId) return; this->ids.uplinkId = uplinkId; - advertise(); + changed = true; } bool is_following() { - return this->ids.uplinkId != 0; + return this->ids.uplinkId != 0 && this->ids.uplinkId != this->ids.id; } // ====== CALLBACKS ======= @@ -352,8 +361,8 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { memcpy(&data_ids, data.c_str(), data.length()); Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); - if (data_ids.id >= ids.uplinkId) { - follow(data_ids.id); + if (data_ids.uplinkId >= ids.uplinkId) { + follow(data_ids.uplinkId); uplink_ping(); } From 00b2a4f2ccac6cfe587a2e06bcd5157d95d02582 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 23 Jul 2022 00:00:03 -0700 Subject: [PATCH 037/263] Update status via mesh network --- usermods/Tubes/Tubes.h | 5 +- usermods/Tubes/bluetooth.h | 316 ++++++++++++++++++++++++++-------- usermods/Tubes/controller.h | 70 ++------ usermods/Tubes/debug.h | 25 +-- usermods/Tubes/global_state.h | 9 + usermods/Tubes/radio.h | 202 ---------------------- 6 files changed, 276 insertions(+), 351 deletions(-) delete mode 100644 usermods/Tubes/radio.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index d82375b659..303666c9bb 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -18,15 +18,13 @@ #include "led_strip.h" #include "controller.h" -#include "radio.h" #include "debug.h" class TubesUsermod : public Usermod { private: BeatController beats; - Radio radio; - PatternController controller = PatternController(MAX_REAL_LEDS, &beats, &radio); + PatternController controller = PatternController(MAX_REAL_LEDS, &beats); DebugController debug = DebugController(&controller); int* master = NULL; /* master.h deleted */ @@ -55,7 +53,6 @@ class TubesUsermod : public Usermod { beats.update(); controller.update(); debug.update(); - radio.update(); // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 1784bc57a3..e2a40d9355 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -2,25 +2,54 @@ #include #include +#include "global_state.h" +#define UPDATE_RATE 2000 // Rate at which uplink is queried for data +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +#define CURRENT_MESH_VERSION 1 +#define MAX_CONNECTED_CLIENTS 3 + +#define DATA_UPDATE_SERVICE "D00B" typedef uint16_t MeshId; typedef struct { - MeshId id = 0; - MeshId uplinkId = 0; -} MeshIds; + MeshId id = 0; + MeshId uplinkId = 0; + uint8_t version = CURRENT_MESH_VERSION; +} MeshNodeHeader; +typedef struct { + MeshNodeHeader header; + TubeState current; + TubeState next; +} MeshStorage; -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +// Asynchronous queue handling +typedef struct { + MeshId id; + NimBLEAddress address; +} MeshUpdateRequest; + +class MessageReceiver { + public: + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + +static TaskHandle_t xUpdaterTaskHandle; +QueueHandle_t UpdaterQueue = xQueueCreate(5, sizeof(MeshUpdateRequest)); +void procUpdaterTask(void* pvParameters); /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ class ServerCallbacks: public NimBLEServerCallbacks { void onConnect(NimBLEServer* pServer) { Serial.println("Client connected"); - Serial.println("Multi-connect support: start advertising"); - NimBLEDevice::startAdvertising(); + if (pServer->getConnectedCount() < MAX_CONNECTED_CLIENTS) + NimBLEDevice::startAdvertising(); }; /** Alternative onConnect() method to extract details of the connection. @@ -39,11 +68,6 @@ class ServerCallbacks: public NimBLEServerCallbacks { pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60); }; - void onDisconnect(NimBLEServer* pServer) { - Serial.println("Client disconnected - start advertising"); - NimBLEDevice::startAdvertising(); - }; - void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) { Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); }; @@ -87,6 +111,8 @@ class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { Serial.print(pCharacteristic->getUUID().toString().c_str()); Serial.print(": onWrite(), value: "); Serial.println(pCharacteristic->getValue().c_str()); + + ESP.restart(); }; /** Called before notification or indication is sent, @@ -149,25 +175,71 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; +NimBLEClient* connectToServer(NimBLEAddress &peer_address) { + NimBLEClient* pClient = nullptr; + bool known_server = false; + + // Check if we have a client we should reuse + if (NimBLEDevice::getClientListSize()) { + // If we already know this peer, send false as the second argument in connect() + // to prevent refreshing the service database. This saves considerable time and power. + pClient = NimBLEDevice::getClientByPeerAddress(peer_address); + if (pClient) + known_server = true; + else + pClient = NimBLEDevice::getDisconnectedClient(); + + if (pClient) { + if (pClient->connect(peer_address, known_server)) + return pClient; + + // Failed to connect. Just drop the client rather than deleting it. + return NULL; + } + } + + // No client to reuse; create a new one - if we can. + if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { + Serial.println("Max clients reached - no more connections available"); + return NULL; + } + pClient = NimBLEDevice::createClient(); + + // Set up this client and attempt to connect. + pClient->setConnectionParams(12,12,0,51); + pClient->setConnectTimeout(5); + if (!pClient->connect(peer_address)) { + // Created a client but failed to connect, don't need to keep it as it has no data. + NimBLEDevice::deleteClient(pClient); + return NULL; + } + + return pClient; +} + + class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { public: bool alive = false; // true if radio booted up bool changed = false; - MeshIds ids; - byte buffer[100]; + MeshNodeHeader ids; char node_name[20]; uint16_t serviceUUID = 0xD00F; - NimBLEServer* pServer = NULL; - NimBLEService* pService = NULL; - NimBLEScan* pScanner = NULL; + NimBLEServer* pServer = nullptr; + NimBLEService* pService = nullptr; + NimBLEScan* pScanner = nullptr; + NimBLEAddress uplink_address; + + MessageReceiver *receiver = nullptr; Timer uplinkTimer; + Timer updateTimer; - MeshId newMeshId() { - return random(0, 4000); + BLEMeshNode(MessageReceiver *receiver) { + this->receiver = receiver; } void advertise() { @@ -182,7 +254,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { auto service_data = std::string((char *)&ids, sizeof(ids)); sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); - // // // Reset the device name + // Reset the device name: // NimBLEDevice::deinit(false); // NimBLEDevice::init(node_name); @@ -191,8 +263,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); pAdvertising->start(); - Serial.printf("Advertising %s", node_name); - Serial.println(); + Serial.printf("Advertising %s\n", node_name); } void init_service() { @@ -213,14 +284,20 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pServer = NimBLEDevice::createServer(); pServer->setCallbacks(new ServerCallbacks()); + pServer->advertiseOnDisconnect(true); - pService = pServer->createService("D00B"); + pService = pServer->createService(DATA_UPDATE_SERVICE); NimBLECharacteristic* pCharacteristic = pService->createCharacteristic( "FEED", - NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + NIMBLE_PROPERTY::READ ); + pCharacteristic->setValue(""); + pCharacteristic->setCallbacks(&chrCallbacks); - pCharacteristic->setValue("Test"); + pCharacteristic = pService->createCharacteristic( + "F00D", + NIMBLE_PROPERTY::WRITE + ); pCharacteristic->setCallbacks(&chrCallbacks); /** Start the services when finished creating all Characteristics and Descriptors */ @@ -238,34 +315,67 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void init_scanner() { NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); - NimBLEDevice::setScanDuplicateCacheSize(200); + NimBLEDevice::setScanDuplicateCacheSize(20); pScanner = NimBLEDevice::getScan(); //create new scan // Set the callback for when devices are discovered, no duplicates. pScanner->setAdvertisedDeviceCallbacks(this, false); pScanner->setActiveScan(false); // Don't request data (it uses more energy) - pScanner->setInterval(200); // How often the scan occurs / switches channels; in milliseconds, - pScanner->setWindow(80); // How long to scan during the interval; in milliseconds. + pScanner->setInterval(97); // How often the scan occurs / switches channels; in milliseconds, + pScanner->setWindow(37); // How long to scan during the interval; in milliseconds. pScanner->setMaxResults(0); // do not store the scan results, use callback only. } + void init_updater() { + xTaskCreate( + procUpdaterTask, /* Function to implement the task */ + "UpdaterTask", /* Name of the task */ + 3840, /* Stack size in bytes */ + this, /* Task input parameter */ + tskIDLE_PRIORITY+1, /* Priority of the task */ + &xUpdaterTaskHandle /* Task handle. */ + ); + } + void init() { esp_wifi_set_ps(WIFI_PS_MIN_MODEM); esp_coex_preference_set(ESP_COEX_PREFER_BT); NimBLEDevice::init(std::string("Tube")); init_scanner(); init_service(); + init_updater(); this->alive = true; } - void uplink_ping() { - // Track the last time we received a message from our master - this->uplinkTimer.start(UPLINK_TIMEOUT); + void onPeerPing(MeshNodeHeader* pRemoteNode, NimBLEAdvertisedDevice* pAdvertisedDevice) { + Serial.printf("Found %03X/%03X at %s\n", + pRemoteNode->id, + pRemoteNode->uplinkId, + std::string(pAdvertisedDevice->getAddress()).c_str() + ); + + if (pRemoteNode->id == ids.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + if (pRemoteNode->id > ids.id && pRemoteNode->id > ids.uplinkId) { + follow(pRemoteNode->id, pAdvertisedDevice); + } + + if (pRemoteNode->id == ids.uplinkId) { + this->onUplinkAlive(); + } + } + + void setup() { + this->reset(); + Serial.println("Mesh: ok"); } void update() { - // Don't do anything for the first half-second to avoid crashing WiFi - if (millis() < 500) + // Don't do anything for the first second to avoid crashing WiFi + if (millis() < 1000) return; if (!this->alive) { @@ -278,6 +388,18 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { follow(0); } + if (this->ids.uplinkId && this->updateTimer.ended()) { + MeshUpdateRequest request = { + .id = this->ids.uplinkId, + .address = this->uplink_address + }; + if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { + Serial.println("Update queue is full!"); + } + updateTimer.snooze(UPDATE_RATE); + } + + // If any actions caused the service to change, re-advertise with new values if (changed) { advertise(); changed = false; @@ -289,20 +411,10 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } } - void broadcast(byte *data, int size) { - if (size > sizeof(buffer)) { - Serial.println("Too much data"); - return; - } - - memset(buffer, 0, sizeof(buffer)); - memcpy(buffer, data, size); - - if (!pServer) - return; - + void update_node_storage(TubeState ¤t, TubeState &next) { // Broadcast the current effect state to every connected client - if (pServer->getConnectedCount() == 0) + + if (!pServer || pServer->getConnectedCount() == 0) return; if (!pService) @@ -312,64 +424,116 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if(!pCharacteristic) return; - // Update the characteristic - pCharacteristic->setValue(buffer); - pCharacteristic->notify(true); + // Store this data in the characteristic + MeshStorage storage = { + .header = this->ids, + .current = current, + .next = next + }; + pCharacteristic->setValue(storage); } void reset(MeshId id = 0) { if (id == 0) - id = newMeshId(); - Serial.printf("My new ID is %03X", id); - + id = random(256, 4000); // Leave room at bottom and top of 12 bits this->ids.id = id; - MeshId uplinkId = this->ids.uplinkId; - if (id > uplinkId) - uplinkId = id; - follow(uplinkId); + follow(0); changed = true; } - void follow(MeshId uplinkId) { - if (uplinkId == 0) { - // Following zero means "follow yourself" - uplinkId = this->ids.id; - } + void follow(MeshId uplinkId, NimBLEAdvertisedDevice* pAdvertisedDevice = NULL) { + // Following zero means you have no uplink + // Update uplink device address + if (uplinkId && pAdvertisedDevice) + this->uplink_address = pAdvertisedDevice->getAddress(); + else + this->uplink_address = NimBLEAddress(); + + // Update uplink ID if (this->ids.uplinkId == uplinkId) return; - this->ids.uplinkId = uplinkId; changed = true; } bool is_following() { - return this->ids.uplinkId != 0 && this->ids.uplinkId != this->ids.id; + return this->ids.uplinkId != 0; } // ====== CALLBACKS ======= - void onResult(NimBLEAdvertisedDevice* advertisedDevice) { - if (!advertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) + void onResult(NimBLEAdvertisedDevice* pAdvertisedDevice) { + // Discovered a peer via scanning. + + if (!pAdvertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) return; - // Make sure it's booted up and advertising data - auto data = advertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); - if (!data.length()) + // Make sure it's booted up and advertising Mesh IDs + auto data = pAdvertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); + if (data.length() != sizeof(MeshNodeHeader)) + return; + MeshNodeHeader* pRemoteNode = (MeshNodeHeader *)data.c_str(); + if (pRemoteNode->version != this->ids.version) return; - MeshIds data_ids; - memcpy(&data_ids, data.c_str(), data.length()); - Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); + this->onPeerPing(pRemoteNode, pAdvertisedDevice); + } - if (data_ids.uplinkId >= ids.uplinkId) { - follow(data_ids.uplinkId); - uplink_ping(); + void onUpdateData(MeshUpdateRequest &request, MeshStorage &storage) { + if (request.id != storage.header.id) { + Serial.println("Uplink is invalid!"); + // The remote server has changed its ID. We need to adapt. + follow(0); + changed = true; + return; } + this->onUplinkAlive(); - Serial.printf("Found: %s: %s\n", - std::string(advertisedDevice->getAddress()).c_str(), - std::string(advertisedDevice->getName()).c_str() + // Process the command + this->receiver->onCommand( + storage.header.id, + COMMAND_UPDATE, + &storage.current + ); + this->receiver->onCommand( + storage.header.id, + COMMAND_NEXT, + &storage.next ); } + void onUplinkAlive() { + // Track the last time we received a message from our uplink + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + }; + + +// UPDATER +// This is an async task handler that awaits requests to update data, +// then connects to the requested server and fetches the data. +void procUpdaterTask(void* pvParameters) { + BLEMeshNode *pNode = (BLEMeshNode*)pvParameters; + MeshUpdateRequest request; + + for (;;) { + // Wait to be told to update (the queue blocks) + xQueueReceive(UpdaterQueue, &request, portMAX_DELAY); + + // Got a request to update, so try to connect and pull down data. + auto uplink_address = request.address; + auto pClient = connectToServer(uplink_address); + if (!pClient) + continue; + + auto pService = pClient->getService(DATA_UPDATE_SERVICE); + if (pService) { + auto pCharacteristic = pService->getCharacteristic("FEED"); + MeshStorage storage = pCharacteristic->readValue(); + pNode->onUpdateData(request, storage); + } + pClient->disconnect(); + } + +} \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8e0c2c435b..0ae346509a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -6,18 +6,11 @@ #include "palettes.h" #include "effects.h" #include "global_state.h" -#include "radio.h" +#include "bluetooth.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; +#define STATUS_UPDATE_PERIOD 1000 -const static CommandId COMMAND_HELLO = 0x00; -const static CommandId COMMAND_OPTIONS = 0x10; -const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_NEXT = 0x30; -const static CommandId COMMAND_RESET = 0xF0; - -const static CommandId COMMAND_BRIGHTNESS = 0x80; -const static CommandId COMMAND_FIREWORK = 0x90; typedef struct { @@ -80,8 +73,8 @@ class PatternController : public MessageReceiver { #endif LEDs *led_strip; BeatController *beats; - Radio *radio; Effects *effects; + BLEMeshNode *mesh; ControllerOptions options; char key_buffer[20] = {0}; @@ -90,15 +83,15 @@ class PatternController : public MessageReceiver { TubeState current_state; TubeState next_state; - PatternController(uint8_t num_leds, BeatController *beats, Radio *radio) { + PatternController(uint8_t num_leds, BeatController *beats) { this->num_leds = num_leds; #ifdef USELCD this->lcd = new Lcd(); #endif this->led_strip = new LEDs(num_leds); this->beats = beats; - this->radio = radio; this->effects = new Effects(); + this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED @@ -112,6 +105,7 @@ class PatternController : public MessageReceiver { void setup(bool isMaster) { + this->mesh->setup(); this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; @@ -125,16 +119,15 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; - Serial.println(F("Patterns: ok")); - - this->radio->setup(this->isMaster); - this->radio->sendCommand(COMMAND_HELLO); - this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to + this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to + Serial.println("Patterns: ok"); } void update() { + this->mesh->update(); + this->read_keys(); // Update patterns to the beat @@ -157,10 +150,9 @@ class PatternController : public MessageReceiver { // Update current status if (this->updateTimer.ended()) { this->send_update(); + this->updateTimer.snooze(STATUS_UPDATE_PERIOD); } - this->radio->receiveCommands(this); - if (this->graphicsTimer.every(REFRESH_PERIOD)) { this->updateGraphics(); } @@ -212,8 +204,7 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state)); - this->updateTimer.snooze(RADIO_SENDPERIOD); + this->mesh->update_node_storage(this->current_state, this->next_state); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -225,7 +216,6 @@ class PatternController : public MessageReceiver { Serial.print(F("E: ")); this->next_state.print(); Serial.print(F(" ")); - this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); Serial.println(); } @@ -341,9 +331,11 @@ class PatternController : public MessageReceiver { } void optionsChanged() { +#ifdef NOT_COMPLETE if (this->isMaster) { this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } +#endif } void setBrightness(uint8_t brightness) { @@ -408,19 +400,12 @@ class PatternController : public MessageReceiver { addFlash(); } - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + virtual void onCommand(MeshId fromId, CommandId command, void *data) { if (fromId) { - Serial.print(F("From ")); - Serial.print(fromId); - Serial.print(F(": ")); + Serial.printf("From %03X: ", fromId); } switch (command) { - case COMMAND_FIREWORK: - Serial.print(F("fireworks")); - this->acknowledge(); - return; - case COMMAND_RESET: Serial.print(F("reset")); return; @@ -431,11 +416,6 @@ class PatternController : public MessageReceiver { return; } - case COMMAND_HELLO: - Serial.print(F("hello")); - this->updateTimer.stop(); - return; - case COMMAND_OPTIONS: { Serial.print(F("options")); memcpy(&this->options, data, sizeof(this->options)); @@ -522,16 +502,6 @@ class PatternController : public MessageReceiver { accum88 arg = this->parse_number(command+1); switch (command[0]) { - case 'f': - this->radio->sendCommandFrom(255, COMMAND_FIREWORK, NULL, 0); - this->onCommand(0, COMMAND_FIREWORK, NULL); - Serial.println(); - break; - - case 'i': - this->radio->mesh_node.reset(arg >> 8); - break; - case 'd': this->setDebugging(!this->options.debugging); break; @@ -609,12 +579,6 @@ class PatternController : public MessageReceiver { this->update_next(); return; - case 'h': - // Pretend to receive a HELLO - this->onCommand(0, COMMAND_HELLO, NULL); - Serial.println(); - return; - case 'g': for (int i=0; i< 10; i++) addGlitter(); @@ -645,7 +609,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + this->mesh->update_node_storage(this->current_state, this->next_state); } }; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 861ae393bd..a6e22109a4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,21 +1,20 @@ #pragma once #include "controller.h" -#include "radio.h" - +#include "bluetooth.h" class DebugController { public: PatternController *controller; LEDs *strip; - Radio *radio; + BLEMeshNode *mesh; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; this->strip = controller->led_strip; - this->radio = controller->radio; + this->mesh = controller->mesh; } void setup() @@ -27,14 +26,14 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("IP: %u.%u.%u.%u ", + Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d\n", + this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], - WiFi.localIP()[3] + WiFi.localIP()[3], + freeMemory() ); - Serial.print(F("Free memory: ")); - Serial.println( freeMemory() ); } // Show the beat on the master OR if debugging @@ -43,22 +42,16 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->radio->mesh_node.ids.id, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->mesh->ids.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->mesh_node.ids.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->mesh->ids.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { this->strip->leds[p3] = CRGB::Yellow; } - if (this->radio->radioRestarts) { - this->strip->leds[1] = CRGB::Red; - } } - if (this->radio->radioFailures && !this->radio->radioRestarts) { - this->strip->leds[0] = CRGB::Red; - } } }; diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index a23a769aae..d75b940f83 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -52,3 +52,12 @@ class TubeState { } }; + +typedef uint8_t CommandId; + +const static CommandId COMMAND_OPTIONS = 0x10; +const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_NEXT = 0x30; +const static CommandId COMMAND_RESET = 0xF0; + +const static CommandId COMMAND_BRIGHTNESS = 0x80; diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h deleted file mode 100644 index 3f44c41754..0000000000 --- a/usermods/Tubes/radio.h +++ /dev/null @@ -1,202 +0,0 @@ -#pragma once - -#include -#include - -#define RADIO_VERSION 1 - -#include "bluetooth.h" - -#define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec - -class Radio; - -typedef uint8_t CommandId; - -#define MESSAGE_DATA_MAX_SIZE 25 -typedef struct { - CommandId command; - byte data[MESSAGE_DATA_MAX_SIZE]; - uint16_t crc = 0; -} RadioMessage; - - -class MessageReceiver { - public: - - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } -}; - -uint16_t calculate_crc( byte *data, byte len ) { - - const unsigned long crc_table[16] = { - 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, - 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, - 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c - }; - - unsigned long crc = ~0L; - - for ( unsigned int index = 0 ; index < len ; ++index ) { - crc = crc_table[( crc ^ data[index] ) & 0x0f] ^ (crc >> 4); - crc = crc_table[( crc ^ ( data[index] >> 4 )) & 0x0f] ^ (crc >> 4); - crc = ~crc; - } - return crc & 65535; -} - - -void printMessageData(RadioMessage &message, int size) { - Serial.print(sizeof(message.data)); - Serial.print(F(":")); - for (unsigned int i = 0; i < sizeof(message.data); i++) { - if (message.data[i] < 16) - Serial.print(F("0")); - Serial.print(message.data[i], HEX); - Serial.print(F(" ")); - } - Serial.print(F("[")); - Serial.print(size); - Serial.print(F("] ")); -} - -class Radio { - public: - bool alive = false; // true if radio booted up - bool reported_no_radio = false; - - BLEMeshNode mesh_node = BLEMeshNode(); - - unsigned long radioFailures = 0; - unsigned long radioRestarts = 0; - - void setup(bool isMaster) { - if (isMaster) - mesh_node.reset(4500); - else - mesh_node.reset(); - - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); - // Start the radio, but mute & listen for a bit - } - - void update() { - mesh_node.update(); - } - - bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) - { - return this->sendCommandFrom(0, command, data, size, relayId); - } - - bool sendCommandFrom(MeshId id, uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) - { - bool sent = false; - if (!this->alive) - return true; - - RadioMessage message; - if (size > sizeof(message.data)) { - Serial.println(F("Too big to send")); - return false; - } - - message.command = command + RADIO_VERSION; - memset(message.data, 0, sizeof(message.data)); - memcpy(message.data, data, size); - uint16_t crc = calculate_crc(message.data, sizeof(message.data)); - message.crc = crc; - - Serial.print(F("[")); - Serial.print(mesh_node.ids.id); - Serial.print(F(": ")); - Serial.print(message.command, HEX); - - mesh_node.broadcast((byte *)&message, sizeof(message)); - - Serial.print(sent ? F(" ok] ") : F(" failed] ")); - return true; - } - - void receiveCommands(MessageReceiver *receiver) - { -#ifdef USERADIO - RadioMessage message; - - if (!this->alive && !this->reported_no_radio) - { - Serial.println(F("No radio")); - this->reported_no_radio = true; - return; - } - - // check for incoming data - while (_radio.hasData()) - { - _radio.readData(&message); - - // Messages must be from a tube with the current version - if ((message.command>>12) != RADIO_VERSION) - return; - - // Ignore relayed messages if we already have a master - if (message.uplinkId && message.uplinkId <= this->uplinkTubeId) - return; - - // Filter out corrupt messages - unsigned long crc = calculate_crc(message.data, sizeof(message.data)); - if (crc != message.crc) { - // Corrupt packet... ignore it. - Serial.print(F("Invalid CRC: ")); - Serial.print(message.crc); - Serial.print(F(" should be ")); - Serial.println(crc); - continue; - } - - // If we detect an ID collision, fix it by choosing a new random one - while (message.tubeId == this->tubeId) { - Serial.print(F("ID collision!")); - this->resetId(); - } - - // Ignore messages from a lower ID - if (message.tubeId < this->tubeId) { - // Don't need to be noisy about relayed messages - if (message.uplinkId == 0) { - Serial.print(F("Ignoring message from ")); - Serial.println(message.tubeId); - } - return; - } - - if (message.tubeId != 255 && message.tubeId > this->uplinkTubeId) { - // Found a new master! - this->uplinkTubeId = message.tubeId; - Serial.print(F("My new uplink is ")); - Serial.println(this->uplinkTubeId); - } - - // Process the command - receiver->onCommand(message.tubeId, message.command & 0xFFF, message.data); - - // Occcasionally relay commands - more frequently if higher ID - uint8_t r = random8(); - if ((r % 3 == 0) && r < this->tubeId) { - Serial.print(F(" (relaying as ")); - Serial.print(this->tubeId); - Serial.print(F(")")); - message.reluplinkIdayId = message.tubeId; - message.tubeId = this->tubeId; - _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); - } - - Serial.println(); - } -#endif - } - -}; From 8fb9fe61eb6f62ce99422da44bbd04ac6a6a4f13 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 23 Jul 2022 09:09:14 -0700 Subject: [PATCH 038/263] Fix controller upates; add better debugging --- usermods/Tubes/bluetooth.h | 6 ++++-- usermods/Tubes/controller.h | 9 +++++---- usermods/Tubes/debug.h | 24 ++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index e2a40d9355..4cc0f69e05 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -34,7 +34,7 @@ typedef struct { class MessageReceiver { public: - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + virtual void onCommand(MeshId fromId, CommandId command, void *data) { // Abstract: subclasses must define } }; @@ -370,6 +370,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void setup() { this->reset(); + this->updateTimer.start(UPDATE_RATE); Serial.println("Mesh: ok"); } @@ -389,6 +390,8 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } if (this->ids.uplinkId && this->updateTimer.ended()) { + this->updateTimer.snooze(UPDATE_RATE); + MeshUpdateRequest request = { .id = this->ids.uplinkId, .address = this->uplink_address @@ -396,7 +399,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { Serial.println("Update queue is full!"); } - updateTimer.snooze(UPDATE_RATE); } // If any actions caused the service to change, re-advertise with new values diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 0ae346509a..ec9e4083af 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -407,17 +407,18 @@ class PatternController : public MessageReceiver { switch (command) { case COMMAND_RESET: - Serial.print(F("reset")); + Serial.println(F("reset")); return; case COMMAND_BRIGHTNESS: { uint8_t *bright = (uint8_t *)data; this->setBrightness(*bright); + Serial.println(); return; } case COMMAND_OPTIONS: { - Serial.print(F("options")); + Serial.println(F("options")); memcpy(&this->options, data, sizeof(this->options)); return; } @@ -427,7 +428,7 @@ class PatternController : public MessageReceiver { memcpy(&this->next_state, data, sizeof(TubeState)); this->next_state.print(); - Serial.print(F(" (obeying)")); + Serial.println(F(" (obeying)")); return; } @@ -437,7 +438,7 @@ class PatternController : public MessageReceiver { TubeState state; memcpy(&state, data, sizeof(TubeState)); state.print(); - Serial.print(F(" (obeying)")); + Serial.println(F(" (obeying)")); // Catch up to this state this->load_pattern(state); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index a6e22109a4..445feb508f 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -3,6 +3,24 @@ #include "controller.h" #include "bluetooth.h" +std::string formatted_time(long ms) { + long secs = ms / 1000; // set the seconds remaining + long mins = secs / 60; //convert seconds to minutes + long hours = mins / 60; //convert minutes to hours + long days = hours / 24; //convert hours to days + + secs = secs % 60; + mins = mins % 60; + hours = hours % 24; + + char buffer[100]; + if (days > 0) + sprintf(buffer, "%ld %02ld:%02ld:%02ld", days, hours, mins, secs); + else + sprintf(buffer, "%02ld:%02ld:%02ld", hours, mins, secs); + return std::string(buffer); +} + class DebugController { public: PatternController *controller; @@ -26,13 +44,14 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d\n", + Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n", this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3], - freeMemory() + freeMemory(), + formatted_time(millis()).c_str() ); } @@ -55,3 +74,4 @@ class DebugController { } }; + From a3b8c73f2d4599f61cf50d611631fb4075881646 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 26 Jul 2022 00:54:48 -0700 Subject: [PATCH 039/263] easier to read debug --- usermods/Tubes/controller.h | 1 + usermods/Tubes/debug.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ec9e4083af..3669c25355 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -201,6 +201,7 @@ class PatternController : public MessageReceiver { } void send_update() { + Serial.print(" "); this->current_state.print(); Serial.print(F(" ")); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 445feb508f..8d47faa0b4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -44,7 +44,7 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n", + Serial.printf("\n=== %s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], From 3b3e98f396cac34a6e922d2af02a49bce7491905 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 26 Jul 2022 09:05:39 -0700 Subject: [PATCH 040/263] Add TimeSync - let's see if it works --- usermods/Tubes/TimeSync/counter.h | 409 +++++++++++++++++++++++++++++ usermods/Tubes/TimeSync/sync.h | 180 +++++++++++++ usermods/Tubes/TimeSync/timesync.h | 397 ++++++++++++++++++++++++++++ usermods/Tubes/bluetooth.h | 2 + usermods/Tubes/controller.h | 3 + usermods/Tubes/effects.h | 2 + usermods/Tubes/pattern.h | 9 + usermods/Tubes/virtual_strip.h | 2 + 8 files changed, 1004 insertions(+) create mode 100644 usermods/Tubes/TimeSync/counter.h create mode 100644 usermods/Tubes/TimeSync/sync.h create mode 100644 usermods/Tubes/TimeSync/timesync.h diff --git a/usermods/Tubes/TimeSync/counter.h b/usermods/Tubes/TimeSync/counter.h new file mode 100644 index 0000000000..a35187de1c --- /dev/null +++ b/usermods/Tubes/TimeSync/counter.h @@ -0,0 +1,409 @@ +/** \file + \brief Counter Math + \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Counter nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +/** \page Counter Math + + Represents an unsigned counter that can roll over from its maximum value + back to zero. A common example is a 32-bit timestamp from GetTickCount() + on Windows, which can roll-over and cause software bugs despite testing. + This also provides compression for counters. + + This class provides: + + Counters of 2 bits through 64 bits e.g. 24-bit counters + + Increment/decrement by 1 or a constant + + Safe comparison operator overloads + + Compression/decompression via truncation + + Unit tested software +*/ + +#include +#include + +// Compiler-specific force inline keyword +#if defined(_MSC_VER) + #define COUNTER_FORCE_INLINE inline __forceinline +#else // _MSC_VER + #define COUNTER_FORCE_INLINE inline __attribute__((always_inline)) +#endif // _MSC_VER + + +//------------------------------------------------------------------------------ +// Counter + +template class Counter +{ +public: + typedef Counter ThisType; + typedef T ValueType; + typedef typename std::make_signed::type SignedType; + + static const unsigned kBits = TkBits; ///< Number of data bits + static const T kMSB = (T)1 << (TkBits - 1); ///< Most Significant Bit + + /// Generate a bit mask with 1s for each data bit + /// The mask gets optimized away when compiler optimizations are enabled + static const T kMask = static_cast(-(int64_t)1) >> (sizeof(T) * 8 - kBits); + + /// Counter value + T Value; + + + //-------------------------------------------------------------------------- + // Assignment + + Counter(T value = 0) + : Value(value & kMask) + { + } + Counter(const ThisType& b) + : Value(b.Value) + { + } + + ThisType& operator=(T value) + { + Value = value & kMask; + return *this; + } + ThisType& operator=(const ThisType b) + { + Value = b.Value; + return *this; + } + + + //-------------------------------------------------------------------------- + // Accessors + + /// Get current value + COUNTER_FORCE_INLINE T ToUnsigned() const + { + return Value; + } + + + //-------------------------------------------------------------------------- + // Increment + + /// Pre-increment + COUNTER_FORCE_INLINE ThisType& operator++() + { + Value = (Value + 1) & kMask; + return *this; + } + + /// Pre-decrement + COUNTER_FORCE_INLINE ThisType& operator--() + { + Value = (Value - 1) & kMask; + return *this; + } + + /// Post-increment + COUNTER_FORCE_INLINE ThisType operator++(int) + { + const T oldValue = Value; + Value = (Value + 1) & kMask; + return oldValue; + } + + /// Post-decrement + COUNTER_FORCE_INLINE ThisType operator--(int) + { + const T oldValue = Value; + Value = (Value - 1) & kMask; + return oldValue; + } + + + //-------------------------------------------------------------------------- + // Addition + + COUNTER_FORCE_INLINE ThisType& operator+=(const ThisType b) + { + Value = (Value + b.Value) & kMask; + return *this; + } + + COUNTER_FORCE_INLINE ThisType operator+(const ThisType b) const + { + return Value + b.Value; // Implicit mask + } + + COUNTER_FORCE_INLINE ThisType& operator-=(const ThisType b) + { + Value = (Value - b.Value) & kMask; + return *this; + } + + COUNTER_FORCE_INLINE ThisType operator-(const ThisType b) const + { + return Value - b.Value; // Implicit mask + } + + + //-------------------------------------------------------------------------- + // Comparison + // We can only compare counters of the same type + + COUNTER_FORCE_INLINE bool operator==(const ThisType b) const + { + return Value == b.Value; + } + COUNTER_FORCE_INLINE bool operator!=(const ThisType b) const + { + return Value != b.Value; + } + COUNTER_FORCE_INLINE bool operator>=(const ThisType b) const + { + const T d = static_cast(Value - b.Value) & kMask; + return d < kMSB; + } + COUNTER_FORCE_INLINE bool operator<(const ThisType b) const + { + const T d = static_cast(Value - b.Value) & kMask; + return d >= kMSB; + } + COUNTER_FORCE_INLINE bool operator<=(const ThisType b) const + { + const T d = static_cast(b.Value - Value) & kMask; + return d < kMSB; + } + COUNTER_FORCE_INLINE bool operator>(const ThisType b) const + { + const T d = static_cast(b.Value - Value) & kMask; + return d >= kMSB; + } + + + //-------------------------------------------------------------------------- + // Counter Compression (Truncation) and Re-Expansion + + /** + These routines will truncate a counter to a smaller number of bits, + and later expand the smaller value back into the original counter value + provided with a reference counter. For example a 64-bit timestamp can + be compressed down to 24 bits, sent over a network, and then expanded + back to the original value given a local time at the receiver. + + This assumes that counters are counting up and that roll-over can only + happen one time. If a counter rolls over twice, then the resulting + expanded counter value will be incorrect. + */ + + /// Compress to smaller counter by truncating + template + COUNTER_FORCE_INLINE SmallerT Truncate() const + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + // Truncate to smaller type + return SmallerT(static_cast(Value)); + } + + /// Expand from truncated counter + /// Bias > 0 can be used to accept values farther in the past + /// Bias < 0 can be used to accept values farther in the future + template + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncatedWithBias( + const ThisType recent, + const SmallerT smaller, + const SignedType bias) + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + /** + The bits in the smaller counter were all truncated from the correct + value, so what needs to be determined now is all the higher bits. + + Examples: + + Recent Smaller => Expanded + ------ ------- -------- + 0x100 0xff 0x0ff + 0x16f 0x7f 0x17f + 0x17f 0x6f 0x16f + 0x1ff 0xa0 0x1a0 + 0x1ff 0x01 0x201 + + The choice to make is between -1, 0, +1 for the next bit position. + + Since we have no information about the high bits, it should be + sufficient to compare the recent low bits with the smaller value + in order to decide which one is correct: + + 00 - ff = -ff -> -1 + 6f - 7f = -10 -> 0 + 7f - 6f = +10 -> 0 + ff - a0 = +5f -> 0 + ff - 01 = +fe -> +1 + */ + + // First insert the low bits to get the default result + ThisType result = smaller.ToUnsigned() | (recent.ToUnsigned() & ~static_cast(SmallerT::kMask)); + + // Grab the low bits of the recent counter + const T recentLow = recent.ToUnsigned() & SmallerT::kMask; + + // If recent - smaller would be negative: + if (recentLow < smaller.ToUnsigned()) + { + // If it is large enough to roll back a MSB: + const T absDiff = smaller.ToUnsigned() - recentLow; + if (absDiff >= static_cast(SmallerT::kMSB - bias)) + result -= static_cast(SmallerT::kMSB) << 1; + } + else + { + // If it is large enough to roll ahead a MSB: + const T absDiff = recentLow - smaller.ToUnsigned(); + if (absDiff > static_cast(SmallerT::kMSB + bias)) + result += static_cast(SmallerT::kMSB) << 1; + } + + return result; + } + + /// Expand from truncated counter without any bias + template + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const SmallerT smaller) + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + ValueType smallerMSB = smaller.Value & SmallerT::kMSB; + SignedType smallerSigned = smaller.Value - (smallerMSB << 1); + + auto smallRecent = static_cast(recent.Value & SmallerT::kMask); + + // Signed gap = partial - prev + auto gap = static_cast(smallerSigned - smallRecent) & SmallerT::kMask; + + ValueType gapMSB = gap & SmallerT::kMSB; + SignedType gapSigned = gap - (gapMSB << 1); + + // Result = recent + gap + return recent.Value + gapSigned; + } + + // Template specialization to optimize cases where the word size matches + // the field size. Otherwise the extra sign handling above is not elided + // by the compiler's optimizer: + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(32 < kBits, "Smaller type must be smaller"); + + const int32_t gap = static_cast(smaller.Value - static_cast(recent.Value)); + return recent + gap; + } + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(16 < kBits, "Smaller type must be smaller"); + + const int16_t gap = static_cast( smaller.Value - static_cast(recent.Value) ); + return recent + gap; + } + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(8 < kBits, "Smaller type must be smaller"); + + const int8_t gap = static_cast(smaller.Value - static_cast(recent.Value)); + return recent + gap; + } + + + static_assert(std::is_pod::value, "Type must be a plain-old data type"); + static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(TkBits > 0, "Invalid input"); + static_assert(sizeof(T) * 8 >= TkBits, "Base type is not wide enough"); + + static_assert(kMask != 0, "bugcheck"); + static_assert(kMSB < kMask, "bugcheck"); + static_assert(kMSB != 0, "bugcheck"); +}; + + +/// Convenience declarations +typedef Counter Counter64; +typedef Counter Counter56; +typedef Counter Counter48; +typedef Counter Counter40; +typedef Counter Counter32; +typedef Counter Counter24; +typedef Counter Counter16; +typedef Counter Counter10; +typedef Counter Counter8; +typedef Counter Counter4; + +static_assert(sizeof(Counter8) == 1, "Unexpected padding"); +static_assert(sizeof(Counter16) == 2, "Unexpected padding"); +static_assert(sizeof(Counter32) == 4, "Unexpected padding"); +static_assert(sizeof(Counter64) == 8, "Unexpected padding"); + +/** + CounterExpand() + + This is a common utility function that expands a 1-7 byte truncated + counter back into a 64-bit (8 byte) counter, based on the largest + counter value seen so far. + + Preconditions: bytes > 0 && bytes < 8 +*/ +COUNTER_FORCE_INLINE Counter64 CounterExpand( + uint64_t largest, + uint64_t partial, + unsigned bytes) +{ + switch (bytes) + { + case 1: return Counter64::ExpandFromTruncated(largest, Counter8((uint8_t)partial)); + case 2: return Counter64::ExpandFromTruncated(largest, Counter16((uint16_t)partial)); + case 3: return Counter64::ExpandFromTruncated(largest, Counter24((uint32_t)partial)); + case 4: return Counter64::ExpandFromTruncated(largest, Counter32((uint32_t)partial)); + case 5: return Counter64::ExpandFromTruncated(largest, Counter40(partial)); + case 6: return Counter64::ExpandFromTruncated(largest, Counter48(partial)); + case 7: return Counter64::ExpandFromTruncated(largest, Counter56(partial)); + default: + break; + } + + return 0; +} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/sync.h b/usermods/Tubes/TimeSync/sync.h new file mode 100644 index 0000000000..da8cb7234f --- /dev/null +++ b/usermods/Tubes/TimeSync/sync.h @@ -0,0 +1,180 @@ +/** \file + \brief TimeSync: Time Synchronization + \copyright Copyright (c) 2017-2019 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of TimeSync nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "timesync.h" + + +//------------------------------------------------------------------------------ +// WindowedMinTS24 + +void WindowedMinTS24::Update( + Counter24 value, + uint64_t timestamp, + const uint64_t windowLengthTime) +{ + const Sample sample(value, timestamp); + + // On the first sample, new best sample, or if window length has expired: + if (!IsValid() || + value <= Samples[0].Value || + Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + Reset(sample); + return; + } + + // Insert the new value into the sorted array + if (value <= Samples[1].Value) + Samples[2] = Samples[1] = sample; + else if (value <= Samples[2].Value) + Samples[2] = sample; + + // Expire best if it has been the best for a long time + if (Samples[0].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + // Also expire the next best if needed + if (Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + Samples[0] = Samples[2]; + Samples[1] = sample; + } + else + { + Samples[0] = Samples[1]; + Samples[1] = Samples[2]; + } + Samples[2] = sample; + return; + } + + // Quarter of window has gone by without a better value - Use the second-best + if (Samples[1].Value == Samples[0].Value && + Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime / 4)) + { + Samples[2] = Samples[1] = sample; + return; + } + + // Half the window has gone by without a better value - Use the third-best one + if (Samples[2].Value == Samples[1].Value && + Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime / 2)) + { + Samples[2] = sample; + } +} + + +//------------------------------------------------------------------------------ +// TimeSynchronizer + +void TimeSynchronizer::OnPeerMinDeltaTS24(Counter24 minDeltaTS24) +{ + LastFC_MinDeltaTS24 = minDeltaTS24; + GotPeerUpdate = true; + + Recalculate(); +} + +unsigned TimeSynchronizer::OnAuthenticatedDatagramTimestamp( + Counter24 remoteSendTS24, + uint64_t localRecvUsec) +{ + const Counter24 localTS24 = (uint32_t)(localRecvUsec >> kTime23LostBits); + + // OWD_i + ClockDelta(L-R)_i = Local Receive Time - Remote Send Time + const Counter24 deltaTS24 = localTS24 - remoteSendTS24; + + WindowedMinTS24Deltas.Update(deltaTS24, localRecvUsec, kDriftWindowUsec); + + Recalculate(); + + // Estimated one-way-delay (OWD) for this datagram in microseconds. + // This does not include processing time only network delay and perhaps + // some delays from the Operating System when it is heavily loaded. + // Set to 0 if trip time is not available + unsigned networkTripUsec = 0; + + if (IsSynchronized()) + { + // This is equivalent to the shortest RTT/2 seen so far by any pair of packets, + // meaning that it is the average of the upstream and downstream OWD. + networkTripUsec = GetMinimumOneWayDelayUsec(); + + // While the OWD is an estimate, the relative delay between that + // smallest packet pair and the current datagram is actually precise: + const Counter24 minDeltaTS24 = GetMinDeltaTS24(); + if (deltaTS24 > minDeltaTS24) + { + const Counter24 relativeTS24 = deltaTS24 - minDeltaTS24; + networkTripUsec += relativeTS24.ToUnsigned() << kTime23LostBits; + } + + // What should happen here is if the delay of each packet varies a lot, then we should + // get pretty accurate OWD for each packet. But if the variance is low and the delays + // for upstream and downstream are asymmetric, then it will underestimate the OWD by + // half of that asymmetry. Hopefully this inaccuracy won't cause problems.. + } + + return networkTripUsec; +} + +void TimeSynchronizer::Recalculate() +{ + if (!WindowedMinTS24Deltas.IsValid() || !GotPeerUpdate) + return; + + // min(OWD_i) + ClockDelta(L-R)_i + const Counter24 minRecvDeltaTS24 = WindowedMinTS24Deltas.GetBest(); + + // min(OWD_j) + ClockDelta(R-L)_j + const Counter24 minSendDeltaTS24 = LastFC_MinDeltaTS24; + + // Assume min(OWD_i) = min(OWD_j): + // min(OWD) ~= (min(OWD_j) + min(OWD_i)) / 2 + const Counter23 minOWD_TS23 = (minSendDeltaTS24 + minRecvDeltaTS24).ToUnsigned() >> 1; + + // Assume ClockDelta(R-L)_j = -ClockDelta(L-R)_i: + // ClockDelta(R-L) ~= (ClockDelta(R-L)_j - ClockDelta(L-R)_i) / 2 + const Counter23 clockDelta_TS23 = (minSendDeltaTS24 - minRecvDeltaTS24).ToUnsigned() >> 1; + + // Calculate the time delta in microseconds + RemoteTimeDeltaUsec = clockDelta_TS23.ToUnsigned() << kTime23LostBits; + + // Calculate the minimum OWD, which may go negative and blow up.. + uint32_t min_owd_usec = minOWD_TS23.ToUnsigned() << kTime23LostBits; + + // If the implied subtraction went negative, correct to zero: + static const uint32_t kSignRolloverThreshold = (1 << 22) << kTime23LostBits; + if (min_owd_usec >= kSignRolloverThreshold) { + min_owd_usec = 0; + } + MinimumOneWayDelayUsec = min_owd_usec; + + Synchronized = true; +} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/timesync.h b/usermods/Tubes/TimeSync/timesync.h new file mode 100644 index 0000000000..48a04f5b31 --- /dev/null +++ b/usermods/Tubes/TimeSync/timesync.h @@ -0,0 +1,397 @@ +/** \file + \brief TimeSync: Time Synchronization + \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of TimeSync nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "counter.h" + +#include + +/** + Time Synchronization Protocol + + -- Motivation: + + Time synchronization is an important core component of an rUDP library, + enabling multiple advantages over reliable UDP libraries without: + + (1) This specific (new) time synchronization works better over cellular + networks than PTP/NTP. + + (2) The API provides time synchronization as a feature for applications, + enabling millisecond-accurate dead reckoning for video games, and + 16-microsecond-accurate timing for scientific applications with 2-3 bytes. + + (3) Peer2Peer NAT hole-punch can be optimized because it can use time + synchronization to initiate probes simultaneously on both peers. + + (4) Delay-based Congestion Control systems should use One Way Delay (OWD) + on each packet as a signal, which allows it to e.g. avoid causing latency + in realtime games while delivering a file transfer in the background. + All existing Delay-based CC algorithms use differential OWD rather than + proper time synchronization. By adding time synchronization, CC becomes + robust to changes in the base OWD as the end-points remain synced. + + -- Background: + + Network time synchronization can be done two ways: + (a) Broadcast - Infeasible on the Internet and so not used. + (b) Assuming that the link is symmetric, and trusting Min(RTT/2) = OWD. + Meaning that existing network time synchronization protocols like NTP and + PTP work by sending multiple probes, and then taking the probe with the + smallest round trip time to be the best data to use in the set of probes. + + Time synchronization at higher resolutions needs to be performed constantly + because clocks drift at a rate of about 1 millisecond per 10 seconds. + So, a common choice for reliable UDP game protocols is to probe for time + synchronization purposes (running NTP all the time) at a fixed interval of + e.g. 5 to 10 seconds. + + The disadvantage of all of these existing approaches is that they only use + a finite number of probes, and all probes may be slightly skewed, + especially when jitter is present, or cross-traffic, or self-congestion + from a file transfer. + + For cellular networks, existing time synchronization approaches provide + degraded results due to the 4-10 millisecond jitter on every packet. + "An End-to-End Measurement Study of Modern Cellular Data Networks" (2014) + https://www.comp.nus.edu.sg/~bleong/publications/pam14-ispcheck.pdf + + when a link is asymmetrical there is no known practical method for + performing time synchronization between two peers that meet on the Internet, + so in that case a best effort is done, and at least it will be consistent. + + -- Algorithm: + + This TimeSync protocol overcomes this jitter problem by using a massive + number of probes (every packet is a probe), greatly increasing the odds + of a minimal RTT probe. + + How it works is that every packet has a 3 byte microsecond timestamp on it, + large enough to prevent roll-over. Both sides record the receive time of + each packet as early as possible, and then throw the packet onto another + thread to process, so that the network delay is more accurate and each + client of a server can run in parallel. + + While each packet is being processed the difference in send and receive + times are compared with prior such differences. And a windowed minimum + of these differences is updated. Periodically this minimum difference + is reported to the remote peer so that both sides have both the minimum + (outgoing - incoming) difference and the minimum (incoming - outgoing) + difference. + + These minimum differences correspond to the shortest trips each way. + Effectively, it turns every single packet into a time synchronization + probe, guaranteeing that it gets the best result possible. + + -- The (Simple) Math for TimeSync: + + We measure (Smallest C2S Delta) and (Smallest S2C Delta) + through the per-packet timestamps. + + C2S = (Smallest C2S Delta) + = (Server Time @ Client Send Time) + (C2S Propagation Delay) - (Client Send Time) + = (C2S Propagation Delay) + (Clock Delta) + + S2C = (Smallest S2C Delta) + = (Client Time @ Server Send Time) + (S2C Propagation Delay) - (Server Send Time) + = (S2C Propagation Delay) - (Clock Delta) + + Such that (Propagation Delay) for each direction is minimized + independently as described previously. + + We want to solve for (Clock Delta) but there is a problem: + + Note that in the definition of C2S and S2C there are three unknowns and + only two measurements. To resolve this problem we make the assumption + that the min. propagation delays are almost the same in each direction. + + And so: + + (S2C Propagation Delay) approx equals (C2S Propagation Delay). + + Thus we can simply write: + + (Clock Delta) = (C2S - S2C) / 2 + + This gives us 23 bits of the delta between clocks, since the division + by 2 (right shift) pulls in an unknown high bit. The effect of any + link asymmetry is halved as a side effect, helping to minimize it. + + -- The (Simple) Math for Network Trip Time: + + It also provides a robust estimate of the "speed of light" between two + network hosts (minimal one-way delay). This information is then used to + calculate the network trip time for every packet that arrives: + + Let DD = Distance from the current packet timestamp difference and minimal. + DD = (Packet Receive - Packet Send) - Min(Packet Receive - Packet Send) + Packet trip time = (Minimal one-way delay) + DD. +*/ + + +//------------------------------------------------------------------------------ +// Constants + +/// Default One Way Delay (OWD) to return before time sync completes +static const uint32_t kDefaultOWDUsec = 200 * 1000; ///< 200 ms + +/// Number of bits removed from the low end of the microsecond timestamp +static const unsigned kTime23LostBits = 3; + +/// Largest 23-bit counter difference value considered positive +static const unsigned kTime23Bias = 0x200000; + +/// Error bound for 23-bit timestamps <= 8*2-1 = 15 microseconds +static const unsigned kTime23ErrorBound = (1 << kTime23LostBits) * 2 - 1; + +/// Number of bits removed from the low end of the microsecond timestamp +static const unsigned kTime16LostBits = 9; + +/// Largest 16-bit counter difference value considered positive +static const unsigned kTime16Bias = 0x4000; + +/// Error bound for 16-bit timestamps <= 512*2-1 = 1.023 milliseconds +static const unsigned kTime16ErrorBound = (1 << kTime16LostBits) * 2 - 1; + +/// Window size for WindowedMinTS24Deltas. +/// Since clocks drift over time, eventually old measurements must be ignored. +/// This is also the longest that a timing measurement will affect time synch. +/// Assumes that clocks drift 1 millisecond every 10 seconds +static const uint64_t kDriftWindowUsec = 10 * 1000 * 1000; ///< 10 seconds + + +//------------------------------------------------------------------------------ +// Types + +/// Use Counter23::Decompress to expand back to 64-bit counters +typedef Counter Counter23; + + +//------------------------------------------------------------------------------ +// WindowedMinTS24 + +/// Windowed minimum in TS24 units +class WindowedMinTS24 +{ +public: + WindowedMinTS24() {} + + struct Sample + { + /// Sample value + Counter24 Value; + + /// Timestamp of data collection + uint64_t Timestamp; + + + /// Default values and initializing constructor + explicit Sample(Counter24 value = 0, uint64_t timestamp = 0) + : Value(value) + , Timestamp(timestamp) + { + } + + /// Check if a timeout expired + inline bool TimeoutExpired(uint64_t now, uint64_t timeout) + { + return (uint64_t)(now - Timestamp) > timeout; + } + }; + + + /// Number of samples collected + static const unsigned kSampleCount = 3; + + /// Sorted samples from smallest to largest + Sample Samples[kSampleCount]; + + + /// Are there any samples? + inline bool IsValid() const + { + return Samples[0].Value != 0; ///< ish + } + + /// Get smallest sample + inline Counter24 GetBest() const + { + return Samples[0].Value; + } + + /// Reset samples + inline void Reset(const Sample sample = Sample()) + { + Samples[0] = Samples[1] = Samples[2] = sample; + } + + /// Update minimum with new value + void Update( + Counter24 value, + uint64_t timestamp, + const uint64_t windowLengthTime); +}; + + +//------------------------------------------------------------------------------ +// TimeSynchronizer + +class TimeSynchronizer +{ +public: + /** + OnPeerMinDeltaTS24() + + Call this when the peer provides its latest 24-bit MinDeltaTS24 value. + The peer should do this periodically, ideally faster in the first minute + and then settling down to once every 2 seconds or so. This can be used + as a keep-alive for example. + + minDeltaTS24: Provide the low 24 bits of the peer's delta value. + */ + void OnPeerMinDeltaTS24(Counter24 minDeltaTS24); + + /// Convert local time in microseconds to a 24-bit datagram timestamp + static inline uint32_t LocalTimeToDatagramTS24(uint64_t localUsec) + { + return (uint32_t)(localUsec >> kTime23LostBits) & 0x00ffffff; + } + + /** + OnAuthenticatedDatagramTimestamp() + + Call this when a datagram arrives with an attached 24-bit timestamp. + Ideally every UDP/IP datagram we receive will have a timestamp. + It is recommended to check if the datagram is from the peer before + accepting the timestamp on the datagram. + + remoteSendTS24: The 24-bit timestamp attached to an incoming datagram. + localRecvUsec: A recent timestamp in microsecond units. + + Returns estimated one way delay (OWD) in microseconds for this datagram. + Returns 0 if OWD is unavailable. + */ + unsigned OnAuthenticatedDatagramTimestamp( + Counter24 remoteSendTS24, + uint64_t localRecvUsec); + + + /// Get the minimum TS24 (receipt - send) delta seen in the past interval + inline Counter24 GetMinDeltaTS24() const + { + return WindowedMinTS24Deltas.GetBest(); + } + + /// Is time synchronized? + inline bool IsSynchronized() const + { + return Synchronized; + } + + /// Get the minimum one-way delay seen so far. + /// This is equivalent to the shortest RTT/2 seen so far by any pair of packets, + /// meaning that it is the average of the upstream and downstream OWD. + inline uint32_t GetMinimumOneWayDelayUsec() const + { + return MinimumOneWayDelayUsec; + } + + /// Returns 16-bit remote time field to send in a packet + inline uint16_t ToRemoteTime16(uint64_t localUsec) + { + if (!Synchronized) { + return 0; + } + + const uint16_t localTS16 = (uint16_t)(localUsec >> kTime16LostBits); + const uint16_t deltaTS16 = (uint16_t)(RemoteTimeDeltaUsec >> kTime16LostBits); + + return localTS16 + deltaTS16; + } + + /// Returns local time given local time from packet + inline uint64_t FromLocalTime16( + uint64_t localUsec, + Counter16 timestamp16) + { + return Counter64::ExpandFromTruncatedWithBias( + localUsec >> kTime16LostBits, + timestamp16, + kTime16Bias).ToUnsigned() << kTime16LostBits; + } + + /// Returns 23-bit remote time field to send in a packet + inline uint32_t ToRemoteTime23(uint64_t localUsec) + { + if (!Synchronized) { + return 0; + } + + const Counter23 localTS23 = (uint32_t)(localUsec >> kTime23LostBits); + const Counter23 deltaTS23 = RemoteTimeDeltaUsec >> kTime23LostBits; + + return (localTS23 + deltaTS23).ToUnsigned(); + } + + /// Returns local time given remote time from packet + inline uint64_t FromLocalTime23( + uint64_t localUsec, + Counter23 timestamp23) + { + return Counter64::ExpandFromTruncatedWithBias( + localUsec >> kTime23LostBits, + timestamp23, + kTime23Bias).ToUnsigned() << kTime23LostBits; + } + +protected: + /// Synchronized? + std::atomic Synchronized = ATOMIC_VAR_INIT(false); + + /// Calculated delta = (Remote time - Local time) + std::atomic RemoteTimeDeltaUsec = ATOMIC_VAR_INIT(0); ///< usec + + /// Calculated minimum OWD + std::atomic MinimumOneWayDelayUsec = ATOMIC_VAR_INIT(kDefaultOWDUsec); ///< in usec + + /// Windowed minimum value for received packet timestamp deltas + /// Keep track of the smallest (receipt - send) time delta seen so far + WindowedMinTS24 WindowedMinTS24Deltas; ///< in Timestamp24 units + + /// Keep a copy of the last MinDeltaUsec from the flow control data from peer + Counter24 LastFC_MinDeltaTS24 = 0; + + /// Is peer update received yet? + bool GotPeerUpdate = false; + + + /// Recalculate MinimumOneWayDelayUsec and RemoteTimeDeltaUsec + void Recalculate(); +}; \ No newline at end of file diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 4cc0f69e05..198e5c8fc3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -4,6 +4,8 @@ #include #include "global_state.h" +#include "TimeSync/sync.h" + #define UPDATE_RATE 2000 // Rate at which uplink is queried for data #define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost #define CURRENT_MESH_VERSION 1 diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3669c25355..d557af6524 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -356,6 +356,9 @@ class PatternController : public MessageReceiver { } SyncMode randomSyncMode() { + #ifdef TESTING_PATTERNS + return All; + #endif uint8_t r = random8(128); if (r < 40) return SinDrift; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 245510aa75..147196c334 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -131,6 +131,7 @@ typedef struct { static const EffectDef gEffects[] = { {{None}, {LongDuration}}, +#ifndef TESTING_PATTERNS {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, @@ -150,5 +151,6 @@ static const EffectDef gEffects[] = { {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, +#endif }; const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 77fdf883ed..a5b2409196 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -79,6 +79,11 @@ void biwave(VirtualStrip *strip) } } +void tick(VirtualStrip *strip) { + strip->fill(CRGB::Black); + strip->leds[strip->beat % 16] = CRGB::White; +} + void sinelon(VirtualStrip *strip) { // a colored dot sweeping back and forth, with fading trails @@ -162,6 +167,9 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. PatternDef gPatterns[] = { +#ifdef TESTING_PATTERNS + {tick, LongDuration}, +#else {drawNoise, {ShortDuration}}, {drawNoise, {ShortDuration}}, {drawNoise, {MediumDuration}}, @@ -180,6 +188,7 @@ PatternDef gPatterns[] = { {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, {bpm_palette, {MediumDuration, HighEnergy}} +#endif }; /* diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 2312724709..66d8b97baa 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -7,6 +7,8 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 +#define TESTING_PATTERNS + class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); From 277be7a208559690e373bcc9642eff7fac9e28c6 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 2 Aug 2022 22:01:40 -0700 Subject: [PATCH 041/263] Make the Tube mode use the current WLED palette --- usermods/Tubes/controller.h | 19 +++++++++++++++---- usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/virtual_strip.h | 19 ++++++++++++++++--- wled00/FX.h | 21 +++++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index d557af6524..8e4f220569 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1,5 +1,8 @@ #pragma once +#include "wled.h" +#include "FX.h" + #include "beats.h" #include "pattern.h" @@ -239,6 +242,8 @@ class PatternController : public MessageReceiver { this->background_changed(); } + // Choose the pattern to display at the next pattern cycle + // Return the number of phrases until the next pattern cycle uint16_t set_next_pattern(uint16_t phrase) { uint8_t pattern_id = random8(gPatternCount); PatternDef def = gPatterns[pattern_id]; @@ -260,23 +265,27 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) + if (this->current_state.palette_id == tube_state.palette_id) { + Serial.println("Nope, don't change"); return; + } this->current_state.palette_phrase = tube_state.palette_phrase; this->_load_palette(tube_state.palette_id); } void _load_palette(uint8_t palette_id) { - this->current_state.palette_id = palette_id % gGradientPaletteCount; + this->current_state.palette_id = palette_id; Serial.print(F("Change palette")); this->background_changed(); } + // Choose the palette to display at the next palette cycle + // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return random8(4,40); + return 1; // random8(4,40); } void load_effect(TubeState &tube_state) { @@ -299,6 +308,8 @@ class PatternController : public MessageReceiver { this->effects->load(this->current_state.effect_params); } + // Choose the effect to display at the next effect cycle + // Return the number of phrases until the next effect cycle uint16_t set_next_effect(uint16_t phrase) { uint8_t effect_num = random8(gEffectCount); @@ -320,7 +331,7 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - background.palette = gGradientPalettes[this->current_state.palette_id]; + background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; // re-use virtual strips to prevent heap fragmentation diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index ddbca2fd4b..58252d44d4 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -3,7 +3,7 @@ #define USE_WLED #include "wled.h" -#define MAX_REAL_LEDS 64 +#define MAX_REAL_LEDS 100 class LEDs { diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 66d8b97baa..e53bf82d05 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -3,11 +3,12 @@ #include "util.h" #include "options.h" #include "beats.h" +#include "palettes.h" #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -#define TESTING_PATTERNS +//#define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -15,7 +16,7 @@ typedef void (*BackgroundFn)(VirtualStrip *strip); class Background { public: BackgroundFn animate; - CRGBPalette16 palette; + uint8_t palette_id; SyncMode sync=All; }; @@ -72,6 +73,12 @@ class VirtualStrip { this->fader = 0; this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; + +#ifdef MASTER_TUBE + // Interface with WLED + WS2812FX::load_palette(background.palette_id); + stateChanged = true; +#endif } void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) @@ -157,7 +164,13 @@ class VirtualStrip { } CRGB palette_color(uint8_t c, uint8_t offset=0) { - return ColorFromPalette( this->background.palette, c + offset ); +#define WLED_COLORS +#ifdef WLED_COLORS + return WS2812FX::get_palette_crgb(c + offset); +#else + CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; + return ColorFromPalette( palette, c + offset ); +#endif } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { diff --git a/wled00/FX.h b/wled00/FX.h index ddb4bd1225..373f01f0ad 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -880,6 +880,27 @@ class WS2812FX { // 96 bytes void estimateCurrentAndLimitBri(void); + + CRGB external_buffer[EXTERNAL_BUFFER_SIZE]; // 4 bytes per element + + public: + static CRGB* get_external_buffer() { + return instance->external_buffer; + } + static CRGB get_palette_crgb(uint16_t c) { + uint32_t color = instance->color_from_palette(c, false, true, 255); + return instance->col_to_crgb(color); + } + static void load_palette(uint8_t palette_id) { + for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + WS2812FX::Segment& seg = instance->getSegment(i); + seg.palette = palette_id; + } + } + static WS2812FX* get_strip() { + return instance; + } + }; extern const char JSON_mode_names[]; From d7691f6d3afa74600194c0b3ae7145cdd8fe1182 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 2 Aug 2022 23:01:26 -0700 Subject: [PATCH 042/263] Fix Tube mode --- usermods/Tubes/controller.h | 6 ++---- usermods/Tubes/pattern.h | 9 ++++----- usermods/Tubes/virtual_strip.h | 4 ++-- wled00/FX.h | 4 ++-- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8e4f220569..81fb462037 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -265,10 +265,8 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) { - Serial.println("Nope, don't change"); + if (this->current_state.palette_id == tube_state.palette_id) return; - } this->current_state.palette_phrase = tube_state.palette_phrase; this->_load_palette(tube_state.palette_id); @@ -285,7 +283,7 @@ class PatternController : public MessageReceiver { // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return 1; // random8(4,40); + return random8(4,40); } void load_effect(TubeState &tube_state) { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index a5b2409196..d9208c8a4a 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -97,8 +97,7 @@ void bpm_palette(VirtualStrip *strip) { uint8_t beat = strip->bpm_sin16(64, 255); for (int i = 0; i < strip->num_leds; i++) { - CRGB c = strip->palette_color(i*2, strip->hue); - nscale8x3(c.r, c.g, c.b, beat-strip->hue+(i*10)); + CRGB c = strip->palette_color(i*2, strip->hue, beat-strip->hue+(i*10)); strip->leds[i] = c; } } @@ -177,13 +176,13 @@ PatternDef gPatterns[] = { {drawNoise, {MediumDuration}}, {drawNoise, {LongDuration}}, {drawNoise, {LongDuration}}, - {rainbow, {ShortDuration}}, + // {rainbow, {ShortDuration}}, {confetti, {ShortDuration}}, {confetti, {MediumDuration}}, {juggle, {ShortDuration}}, - {bpm, {ShortDuration}}, - {bpm, {MediumDuration, HighEnergy}}, + // {bpm, {ShortDuration}}, + // {bpm, {MediumDuration, HighEnergy}}, {palette_wave, {ShortDuration}}, {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index e53bf82d05..bc58e13e10 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -163,10 +163,10 @@ class VirtualStrip { } } - CRGB palette_color(uint8_t c, uint8_t offset=0) { + CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { #define WLED_COLORS #ifdef WLED_COLORS - return WS2812FX::get_palette_crgb(c + offset); + return WS2812FX::get_palette_crgb(c + offset, brightness); #else CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; return ColorFromPalette( palette, c + offset ); diff --git a/wled00/FX.h b/wled00/FX.h index 373f01f0ad..99626ce228 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -887,8 +887,8 @@ class WS2812FX { // 96 bytes static CRGB* get_external_buffer() { return instance->external_buffer; } - static CRGB get_palette_crgb(uint16_t c) { - uint32_t color = instance->color_from_palette(c, false, true, 255); + static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { + uint32_t color = instance->color_from_palette(c, false, true, 255, brightness); return instance->col_to_crgb(color); } static void load_palette(uint8_t palette_id) { From ef348c595df15cb642c7a045a9d98aa2721f78a7 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 3 Aug 2022 01:19:46 -0700 Subject: [PATCH 043/263] Fix palette error --- usermods/Tubes/controller.h | 1 - usermods/Tubes/pattern.h | 3 --- usermods/Tubes/virtual_strip.h | 1 + 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 81fb462037..70aec2ba6a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -292,7 +292,6 @@ class PatternController : public MessageReceiver { this->current_state.effect_params.chance == tube_state.effect_params.chance) return; - this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; this->_load_effect(tube_state.effect_params); } diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index d9208c8a4a..d8a17d13f0 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -176,13 +176,10 @@ PatternDef gPatterns[] = { {drawNoise, {MediumDuration}}, {drawNoise, {LongDuration}}, {drawNoise, {LongDuration}}, - // {rainbow, {ShortDuration}}, {confetti, {ShortDuration}}, {confetti, {MediumDuration}}, {juggle, {ShortDuration}}, - // {bpm, {ShortDuration}}, - // {bpm, {MediumDuration, HighEnergy}}, {palette_wave, {ShortDuration}}, {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index bc58e13e10..8622a72086 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -78,6 +78,7 @@ class VirtualStrip { // Interface with WLED WS2812FX::load_palette(background.palette_id); stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif } From 6ea37c54d46b6a9bf72982c8ddc5d6ca369afd57 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 03:40:28 -0400 Subject: [PATCH 044/263] Get QuickESP comms working --- usermods/Tubes/bluetooth.h | 84 ++++------------- usermods/Tubes/controller.h | 23 ++--- usermods/Tubes/debug.h | 16 ++-- usermods/Tubes/node.h | 183 ++++++++++++++++++++++++++++++++++++ usermods/Tubes/util.h | 13 ++- wled00/wled.cpp | 2 + wled00/wled.h | 2 +- 7 files changed, 231 insertions(+), 92 deletions(-) create mode 100644 usermods/Tubes/node.h diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 198e5c8fc3..4c11d89872 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -4,22 +4,14 @@ #include #include "global_state.h" -#include "TimeSync/sync.h" -#define UPDATE_RATE 2000 // Rate at which uplink is queried for data -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost -#define CURRENT_MESH_VERSION 1 -#define MAX_CONNECTED_CLIENTS 3 +#include "node.h" -#define DATA_UPDATE_SERVICE "D00B" +// #include "TimeSync/sync.h" -typedef uint16_t MeshId; +#define MAX_CONNECTED_CLIENTS 3 -typedef struct { - MeshId id = 0; - MeshId uplinkId = 0; - uint8_t version = CURRENT_MESH_VERSION; -} MeshNodeHeader; +#define DATA_UPDATE_SERVICE "D00B" typedef struct { MeshNodeHeader header; @@ -33,14 +25,6 @@ typedef struct { NimBLEAddress address; } MeshUpdateRequest; -class MessageReceiver { - public: - - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } -}; - static TaskHandle_t xUpdaterTaskHandle; QueueHandle_t UpdaterQueue = xQueueCreate(5, sizeof(MeshUpdateRequest)); void procUpdaterTask(void* pvParameters); @@ -226,7 +210,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { bool changed = false; MeshNodeHeader ids; - char node_name[20]; uint16_t serviceUUID = 0xD00F; @@ -237,14 +220,14 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { MessageReceiver *receiver = nullptr; - Timer uplinkTimer; - Timer updateTimer; - BLEMeshNode(MessageReceiver *receiver) { this->receiver = receiver; } void advertise() { + auto service_data = std::string((char *)&ids, sizeof(ids)); + +#ifdef BLE_MESH if (!pService) return; @@ -253,8 +236,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (!pAdvertising) return; - auto service_data = std::string((char *)&ids, sizeof(ids)); - sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); // Reset the device name: // NimBLEDevice::deinit(false); @@ -264,6 +245,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->stop(); pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); pAdvertising->start(); +#endif Serial.printf("Advertising %s\n", node_name); } @@ -340,12 +322,17 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } void init() { + WiFi.mode(WIFI_AP_STA); + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + +#ifdef BLE_MESH esp_coex_preference_set(ESP_COEX_PREFER_BT); NimBLEDevice::init(std::string("Tube")); init_scanner(); init_service(); init_updater(); +#endif this->alive = true; } @@ -371,8 +358,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } void setup() { - this->reset(); - this->updateTimer.start(UPDATE_RATE); Serial.println("Mesh: ok"); } @@ -385,15 +370,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { this->init(); } - // Check the last time we heard from the uplink node - if (is_following() && this->uplinkTimer.ended()) { - Serial.println("Uplink lost"); - follow(0); - } - - if (this->ids.uplinkId && this->updateTimer.ended()) { - this->updateTimer.snooze(UPDATE_RATE); - +#ifdef BLE_MESH MeshUpdateRequest request = { .id = this->ids.uplinkId, .address = this->uplink_address @@ -401,6 +378,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { Serial.println("Update queue is full!"); } +#endif } // If any actions caused the service to change, re-advertise with new values @@ -409,13 +387,14 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { changed = false; } - if (!this->pScanner->isScanning()) { + if (this->pScanner && !this->pScanner->isScanning()) { // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. this->pScanner->start(0, nullptr, false); } } void update_node_storage(TubeState ¤t, TubeState &next) { +#ifdef BLE_MESH // Broadcast the current effect state to every connected client if (!pServer || pServer->getConnectedCount() == 0) @@ -435,34 +414,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { .next = next }; pCharacteristic->setValue(storage); - } - - void reset(MeshId id = 0) { - if (id == 0) - id = random(256, 4000); // Leave room at bottom and top of 12 bits - this->ids.id = id; - follow(0); - changed = true; - } - - void follow(MeshId uplinkId, NimBLEAdvertisedDevice* pAdvertisedDevice = NULL) { - // Following zero means you have no uplink - - // Update uplink device address - if (uplinkId && pAdvertisedDevice) - this->uplink_address = pAdvertisedDevice->getAddress(); - else - this->uplink_address = NimBLEAddress(); - - // Update uplink ID - if (this->ids.uplinkId == uplinkId) - return; - this->ids.uplinkId = uplinkId; - changed = true; - } - - bool is_following() { - return this->ids.uplinkId != 0; +#endif } // ====== CALLBACKS ======= diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 70aec2ba6a..b1fc151357 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -9,7 +9,7 @@ #include "palettes.h" #include "effects.h" #include "global_state.h" -#include "bluetooth.h" +#include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define STATUS_UPDATE_PERIOD 1000 @@ -21,8 +21,6 @@ typedef struct { uint8_t brightness; } ControllerOptions; -#define NEXT_PATTERN_TIME 53000 -#define NEXT_PALETTE_TIME 27000 #define NUM_VSTRIPS 3 @@ -77,7 +75,7 @@ class PatternController : public MessageReceiver { LEDs *led_strip; BeatController *beats; Effects *effects; - BLEMeshNode *mesh; + LightNode *node; ControllerOptions options; char key_buffer[20] = {0}; @@ -94,7 +92,8 @@ class PatternController : public MessageReceiver { this->led_strip = new LEDs(num_leds); this->beats = beats; this->effects = new Effects(); - this->mesh = new BLEMeshNode(this); + this->node = new LightNode(); + // this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED @@ -108,7 +107,7 @@ class PatternController : public MessageReceiver { void setup(bool isMaster) { - this->mesh->setup(); + this->node->setup(); this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; @@ -129,7 +128,7 @@ class PatternController : public MessageReceiver { void update() { - this->mesh->update(); + this->node->update(); this->read_keys(); @@ -208,7 +207,7 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - this->mesh->update_node_storage(this->current_state, this->next_state); + // this->node->update_node_storage(this->current_state, this->next_state); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -622,13 +621,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->mesh->update_node_storage(this->current_state, this->next_state); + // this->node->update_node_storage(this->current_state, this->next_state); } }; - - - -// What's interesting? -// c53 - clouds -// m4 - swing drift diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 8d47faa0b4..28a8397526 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,7 +1,7 @@ #pragma once #include "controller.h" -#include "bluetooth.h" +#include "node.h" std::string formatted_time(long ms) { long secs = ms / 1000; // set the seconds remaining @@ -25,14 +25,14 @@ class DebugController { public: PatternController *controller; LEDs *strip; - BLEMeshNode *mesh; + LightNode *node; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; this->strip = controller->led_strip; - this->mesh = controller->mesh; + this->node = controller->node; } void setup() @@ -44,8 +44,10 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", - this->controller->mesh->node_name, + Serial.printf("\n=== %s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", + this->controller->node->node_name, + WiFi.status(), + WiFi.channel(), WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], @@ -61,10 +63,10 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->mesh->ids.id, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->node->header.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->mesh->ids.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h new file mode 100644 index 0000000000..de7621322d --- /dev/null +++ b/usermods/Tubes/node.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#if defined ESP32 +#include +#include +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#else +#error "Unsupported platform" +#endif //ESP32 +#include + +#include "global_state.h" + +#define CURRENT_NODE_VERSION 1 +#define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS + +#define UPDATE_RATE 3000 // Rate at which uplink is queried for data +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost + +typedef uint16_t MeshId; + +typedef struct { + MeshId id = 0; + MeshId uplinkId = 0; + uint8_t version = CURRENT_NODE_VERSION; +} MeshNodeHeader; + + +void onDataSent (uint8_t* address, uint8_t status) { + Serial.printf (">> Message sent to " MACSTR ", status: %d\n", MAC2STR (address), status); +} + +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.printf (">> Received %d bytes ", len); + Serial.printf ("\"%.*s\" ", len, data); + Serial.printf ("%s", broadcast ? "broadcast" : "unicast"); + Serial.printf ("@ %d dBm ", rssi); + Serial.printf ("from " MACSTR "\n" , MAC2STR(address)); +} + + +class MessageReceiver { + public: + + virtual void onCommand(MeshId fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + + +#define NODE_STATUS_INIT 0 +#define NODE_STATUS_BROADCASTING 1 +#define NODE_STATUS_QUIET 2 + + +class LightNode { + public: + MeshNodeHeader header; + char node_name[20]; + + uint8_t status = NODE_STATUS_INIT; + bool statusChanged = false; + + Timer uplinkTimer; + Timer updateTimer; + + void onWifiConnect() { + if (this->status == NODE_STATUS_BROADCASTING) { + Serial.println("Stop Broadcasting"); + quickEspNow.stop(); + } + + Serial.println("Stop broadcasting"); + this->status = NODE_STATUS_QUIET; + } + + void onWifiDisconnect() { + if (this->status == NODE_STATUS_BROADCASTING) + return; + + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + + Serial.println("Broadcasting"); + this->status = NODE_STATUS_BROADCASTING; + } + + void onStatusChange() { + sprintf(this->node_name, + "Tube %03X:%03X", + this->header.id, + this->header.uplinkId + ); + } + + void onUplinkAlive() { + // Track the last time we received a message from our uplink + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + + void setup() { + this->reset(); + this->updateTimer.start(UPDATE_RATE); + this->status = NODE_STATUS_INIT; + this->onStatusChange(); + + quickEspNow.onDataRcvd(onDataReceived); + quickEspNow.onDataSent(onDataSent); + + Serial.println("Node: ok"); + } + + void broadcast() { + if (this->status != NODE_STATUS_BROADCASTING) { + Serial.println(">> BC NO"); + return; + } + + static unsigned int counter = 0; + static const String msg = "Hello! "; + + String message = String (msg) + " " + String (counter++); + // WiFi.disconnect (false, true); + if (!quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message.c_str (), message.length ())) { + Serial.printf (">>>>>>>>>> Message sent: %s\n", message.c_str ()); + } else { + Serial.printf (">>>>>>>>>> Message not sent\n"); + } + } + + void update() { + // Don't do anything for the first second, to allow Wifi to settle + if (millis() < 1000) + return; + + // Check the last time we heard from the uplink node + if (is_following() && this->uplinkTimer.ended()) { + Serial.println("Uplink lost"); + this->follow(0); + } + + if (this->updateTimer.ended()) { + if (WiFi.isConnected()) + this->onWifiConnect(); + else + this->onWifiDisconnect(); + + this->broadcast(); + this->updateTimer.snooze(UPDATE_RATE); + } + + if (this->statusChanged) { + this->onStatusChange(); + this->statusChanged = false; + } + } + + void reset(MeshId id = 0) { + if (id == 0) + id = random(256, 4000); // Leave room at bottom and top of 12 bits + this->header.id = id; + this->follow(0); + this->statusChanged = true; + } + + void follow(MeshId uplinkId) { + // Update uplink ID + if (this->header.uplinkId == uplinkId) + return; + + // Following zero means you have no uplink + this->header.uplinkId = uplinkId; + this->statusChanged = true; + } + + bool is_following() { + return this->header.uplinkId != 0; + } +}; \ No newline at end of file diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index 25cbc7a163..60371c52d5 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -2,6 +2,9 @@ #include "wled.h" +// Is this the tube that can control others? +#define MASTER_TUBE + uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { uint16_t rangewidth = highest - lowest; uint16_t scaledbeat = scale16( v, rangewidth ); @@ -11,6 +14,10 @@ uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { #define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) -#define __ESP32__ -#define USTD_OPTION_FS_FORCE_NO_FS -#include \ No newline at end of file +uint32_t freeMemory() { + return ESP.getFreeHeap(); +} + +// #define __ESP32__ +// #define USTD_OPTION_FS_FORCE_NO_FS +// #include \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c9f590fb33..e767b3fe5c 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -449,6 +449,8 @@ void WLED::initAP(bool resetAP) strcpy_P(apPass, PSTR(DEFAULT_AP_PASS)); DEBUG_PRINT(F("Opening access point ")); DEBUG_PRINTLN(apSSID); + DEBUG_PRINT(F("Password ")); + DEBUG_PRINTLN(apPass); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); diff --git a/wled00/wled.h b/wled00/wled.h index 7c43429d0b..011eb88479 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -49,7 +49,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -//#define WLED_DEBUG +#define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS From 57e91e4d3af123e99eba5ec67c3e7834fc46280e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 04:00:24 -0400 Subject: [PATCH 045/263] auto-configure the network --- usermods/Tubes/node.h | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index de7621322d..f8ba49cf83 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -62,11 +62,18 @@ class LightNode { char node_name[20]; uint8_t status = NODE_STATUS_INIT; - bool statusChanged = false; + bool meshChanged = false; Timer uplinkTimer; Timer updateTimer; + void configure_ap() { + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + strcpy(apSSID, ""); + apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; + } + void onWifiConnect() { if (this->status == NODE_STATUS_BROADCASTING) { Serial.println("Stop Broadcasting"); @@ -89,7 +96,7 @@ class LightNode { this->status = NODE_STATUS_BROADCASTING; } - void onStatusChange() { + void onMeshChange() { sprintf(this->node_name, "Tube %03X:%03X", this->header.id, @@ -103,14 +110,17 @@ class LightNode { } void setup() { - this->reset(); + configure_ap(); + this->updateTimer.start(UPDATE_RATE); this->status = NODE_STATUS_INIT; - this->onStatusChange(); + + this->reset(); + this->onMeshChange(); quickEspNow.onDataRcvd(onDataReceived); quickEspNow.onDataSent(onDataSent); - + Serial.println("Node: ok"); } @@ -143,6 +153,11 @@ class LightNode { this->follow(0); } + if (this->meshChanged) { + this->onMeshChange(); + this->meshChanged = false; + } + if (this->updateTimer.ended()) { if (WiFi.isConnected()) this->onWifiConnect(); @@ -152,11 +167,6 @@ class LightNode { this->broadcast(); this->updateTimer.snooze(UPDATE_RATE); } - - if (this->statusChanged) { - this->onStatusChange(); - this->statusChanged = false; - } } void reset(MeshId id = 0) { @@ -164,7 +174,7 @@ class LightNode { id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; this->follow(0); - this->statusChanged = true; + this->meshChanged = true; } void follow(MeshId uplinkId) { @@ -174,7 +184,7 @@ class LightNode { // Following zero means you have no uplink this->header.uplinkId = uplinkId; - this->statusChanged = true; + this->meshChanged = true; } bool is_following() { From 17289ededd99f9f94d285efaa7e29270a2a11125 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 16:32:51 -0400 Subject: [PATCH 046/263] Get syncing working & Speed up color changes --- usermods/Tubes/controller.h | 14 ++-- usermods/Tubes/node.h | 151 ++++++++++++++++++++++++++---------- wled00/wled.h | 2 +- 3 files changed, 119 insertions(+), 48 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b1fc151357..a358bc1daa 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -14,6 +14,8 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define STATUS_UPDATE_PERIOD 1000 +#define MIN_COLOR_CHANGE_PHRASES 2 // 4 +#define MAX_COLOR_CHANGE_PHRASES 4 // 40 typedef struct { @@ -92,7 +94,7 @@ class PatternController : public MessageReceiver { this->led_strip = new LEDs(num_leds); this->beats = beats; this->effects = new Effects(); - this->node = new LightNode(); + this->node = new LightNode(this); // this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { @@ -128,7 +130,7 @@ class PatternController : public MessageReceiver { void update() { - this->node->update(); + this->node->update(this->current_state, this->next_state); this->read_keys(); @@ -282,7 +284,7 @@ class PatternController : public MessageReceiver { // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return random8(4,40); + return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); } void load_effect(TubeState &tube_state) { @@ -411,10 +413,8 @@ class PatternController : public MessageReceiver { addFlash(); } - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - if (fromId) { - Serial.printf("From %03X: ", fromId); - } + virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { + Serial.printf("From %03X/%03X: ", header->id, header->uplinkId); switch (command) { case COMMAND_RESET: diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f8ba49cf83..139bf1407a 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -17,8 +17,9 @@ #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define UPDATE_RATE 3000 // Rate at which uplink is queried for data -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +#define BROADCAST_RATE 3000 // Rate at which to broadcast as leader +#define REBROADCAST_RATE 7000 // Rate at which to re-broadcast as follower +#define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost typedef uint16_t MeshId; @@ -28,26 +29,20 @@ typedef struct { uint8_t version = CURRENT_NODE_VERSION; } MeshNodeHeader; +typedef struct { + MeshNodeHeader header; + TubeState current; + TubeState next; +} NodeMessage; -void onDataSent (uint8_t* address, uint8_t status) { - Serial.printf (">> Message sent to " MACSTR ", status: %d\n", MAC2STR (address), status); -} - -void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - Serial.printf (">> Received %d bytes ", len); - Serial.printf ("\"%.*s\" ", len, data); - Serial.printf ("%s", broadcast ? "broadcast" : "unicast"); - Serial.printf ("@ %d dBm ", rssi); - Serial.printf ("from " MACSTR "\n" , MAC2STR(address)); -} +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); class MessageReceiver { public: - - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } + virtual void onCommand(CommandId command, MeshNodeHeader* header, void *data) { + // Abstract: subclasses must define + } }; @@ -58,14 +53,24 @@ class MessageReceiver { class LightNode { public: + static LightNode* instance; + + MessageReceiver *receiver; MeshNodeHeader header; + char node_name[20]; uint8_t status = NODE_STATUS_INIT; bool meshChanged = false; Timer uplinkTimer; - Timer updateTimer; + Timer broadcastTimer; + + LightNode(MessageReceiver *receiver) { + LightNode::instance = this; + + this->receiver = receiver; + } void configure_ap() { strcpy(clientSSID, ""); @@ -104,45 +109,106 @@ class LightNode { ); } - void onUplinkAlive() { - // Track the last time we received a message from our uplink - this->uplinkTimer.start(UPLINK_TIMEOUT); + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + // Ignore this packet if it couldn't be a mesh report. + if (len != sizeof(NodeMessage)) + return; + + NodeMessage* message = (NodeMessage*)data; + // Serial.printf(">> Received %db ", len); + // Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + // Serial.printf("at " MACSTR, MAC2STR(address)); + // Serial.printf("@ %ddBm: ", rssi); + + // Ignore this packet if wrong version + if (message->header.version != this->header.version) { + // Serial.printf(" header.id <= this->header.uplinkId) { + // Serial.printf(" ignoring\n", rssi); + return; + } + + // Serial.printf(" listening\n", rssi); + this->onPeerPing(&message->header); + + // Execute the received command + MeshId fromId = message->header.uplinkId; + if (!fromId) fromId = message->header.id; + + this->receiver->onCommand( + COMMAND_UPDATE, + &message->header, + &message->current + ); + this->receiver->onCommand( + COMMAND_NEXT, + &message->header, + &message->next + ); + } + + void onPeerPing(MeshNodeHeader* node) { + if (node->id == this->header.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + if (node->id > this->header.uplinkId && node->id > this->header.id) { + Serial.printf("Following %03X:%03X\n", + node->id, + node->uplinkId + ); + + this->follow(node->id); + } + + if (node->id == this->header.uplinkId) { + this->uplinkTimer.start(UPLINK_TIMEOUT); + } } void setup() { configure_ap(); - this->updateTimer.start(UPDATE_RATE); + this->broadcastTimer.start(BROADCAST_RATE); this->status = NODE_STATUS_INIT; this->reset(); this->onMeshChange(); quickEspNow.onDataRcvd(onDataReceived); - quickEspNow.onDataSent(onDataSent); Serial.println("Node: ok"); } - void broadcast() { - if (this->status != NODE_STATUS_BROADCASTING) { - Serial.println(">> BC NO"); + void broadcast(TubeState ¤t, TubeState &next) { + // Don't broadcast if not in broadcast mode + if (this->status != NODE_STATUS_BROADCASTING) return; - } - static unsigned int counter = 0; - static const String msg = "Hello! "; - - String message = String (msg) + " " + String (counter++); - // WiFi.disconnect (false, true); - if (!quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message.c_str (), message.length ())) { - Serial.printf (">>>>>>>>>> Message sent: %s\n", message.c_str ()); + NodeMessage message = { + .header = this->header, + .current = current, + .next = next, + }; + + auto err = quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, + (uint8_t*)&message, sizeof(message)); + if (err) + Serial.printf(">> Broadcast error %d\n", err); + + if (this->is_following()) { + this->broadcastTimer.snooze(REBROADCAST_RATE); } else { - Serial.printf (">>>>>>>>>> Message not sent\n"); + this->broadcastTimer.snooze(BROADCAST_RATE); } } - void update() { + void update(TubeState ¤t, TubeState &next) { // Don't do anything for the first second, to allow Wifi to settle if (millis() < 1000) return; @@ -158,14 +224,13 @@ class LightNode { this->meshChanged = false; } - if (this->updateTimer.ended()) { + if (this->broadcastTimer.ended()) { if (WiFi.isConnected()) this->onWifiConnect(); else this->onWifiDisconnect(); - this->broadcast(); - this->updateTimer.snooze(UPDATE_RATE); + this->broadcast(current, next); } } @@ -190,4 +255,10 @@ class LightNode { bool is_following() { return this->header.uplinkId != 0; } -}; \ No newline at end of file +}; + +LightNode* LightNode::instance = nullptr; + +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + LightNode::instance->onPeerData(address, data, len, rssi, broadcast); +} diff --git a/wled00/wled.h b/wled00/wled.h index 011eb88479..a14e72c20b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -49,7 +49,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -#define WLED_DEBUG +// #define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS From ba12efff3d58cbbb4418d0d3d64ca4bf48751d4f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 17:35:54 -0400 Subject: [PATCH 047/263] Restore tube tech & finish --- usermods/Tubes/virtual_strip.h | 3 ++- wled00/FX.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 8622a72086..5b12411e42 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -8,7 +8,7 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -//#define TESTING_PATTERNS +// #define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -77,6 +77,7 @@ class VirtualStrip { #ifdef MASTER_TUBE // Interface with WLED WS2812FX::load_palette(background.palette_id); + WS2812FX::load_pattern(FX_MODE_EXTERNAL); stateChanged = true; stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif diff --git a/wled00/FX.h b/wled00/FX.h index 99626ce228..52a0ea3284 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -897,6 +897,11 @@ class WS2812FX { // 96 bytes seg.palette = palette_id; } } + static void load_pattern(uint8_t pattern_id) { + for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + instance->setMode(i, pattern_id); + } + } static WS2812FX* get_strip() { return instance; } From 6b1a66724f7df0ba204ed6c73987bd26a0af0173 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 23:55:48 -0400 Subject: [PATCH 048/263] Turn keyboard commands back on. --- usermods/Tubes/controller.h | 9 +++++++-- wled00/wled_serial.cpp | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a358bc1daa..7cff1beab5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -594,7 +594,7 @@ class PatternController : public MessageReceiver { case 'g': for (int i=0; i< 10; i++) addGlitter(); - break; + return; case '?': Serial.println(F("b###.# - set bpm")); @@ -608,6 +608,11 @@ class PatternController : public MessageReceiver { Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); + return; + + default: + Serial.println("dunno?"); + return; } } @@ -621,7 +626,7 @@ class PatternController : public MessageReceiver { } void update_next() { - // this->node->update_node_storage(this->current_state, this->next_state); + this->node->broadcast(this->current_state, this->next_state); } }; diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index a73127e040..2defd62cec 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -36,6 +36,7 @@ void updateBaudRate(uint32_t rate){ void handleSerial() { + return; if (pinManager.isPinAllocated(3)) return; #ifdef WLED_ENABLE_ADALIGHT From d451bf578398a62d7d3a083028652cea7287a581 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 7 Aug 2022 01:19:28 -0400 Subject: [PATCH 049/263] Move overlay effects to a Usermod overlay so they work on any pattern --- usermods/Tubes/Tubes.h | 7 ++++ usermods/Tubes/controller.h | 3 +- usermods/Tubes/effects.h | 4 +- usermods/Tubes/particle.h | 73 ++++++++++++++++++++----------------- wled00/FX.h | 6 +++ 5 files changed, 55 insertions(+), 38 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 303666c9bb..81aebeab47 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -57,6 +57,13 @@ class TubesUsermod : public Usermod { // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us } + + void handleOverlayDraw() + { + // Draw effects layers over whatever WLED is doing. + WS2812FX* leds = &strip; + controller.effects->draw(leds); + } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 7cff1beab5..bde3b66906 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -406,11 +406,10 @@ class PatternController : public MessageReceiver { } this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); - this->effects->draw(this->led_strip->leds, this->num_leds); } virtual void acknowledge() { - addFlash(); + addFlash(CRGB::Green); } virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 147196c334..47308197cd 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -112,11 +112,11 @@ class Effects { } } - void draw(CRGB strip[], uint8_t num_leds) { + void draw(WS2812FX* leds) { uint8_t len = numParticles; for (uint8_t i=0; idrawFn(particle, strip, num_leds); + particle->drawFn(particle, leds); } } diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d31e4185d7..bbe6118475 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -9,10 +9,10 @@ class Particle; -typedef void (*ParticleFn)(Particle *particle, CRGB strip[], uint8_t num_leds); +typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); -extern void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds); -extern void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds); +extern void drawPoint(Particle *particle, WS2812FX* leds); +extern void drawFlash(Particle *particle, WS2812FX* leds); class Particle { @@ -96,59 +96,62 @@ class Particle { return CRGB(r,g,b); } - void draw_with_pen(CRGB strip[], int pos, CRGB color) { + void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { CRGB new_color; - + switch (this->pen) { case Draw: - strip[pos] = color; + new_color = color; break; case Blend: - strip[pos] |= color; + new_color = WS2812FX::get_crgb(pos) | color; break; case Erase: - strip[pos] &= color; + new_color = WS2812FX::get_crgb(pos) & color; break; case Invert: - strip[pos] = -strip[pos]; + new_color = -WS2812FX::get_crgb(pos); break; case Brighten: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - strip[pos] += new_color; + new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); break; } case Darken: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - strip[pos] -= new_color; + new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); break; } case Flicker: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - if (millis() % 2) - strip[pos] -= new_color; - else - strip[pos] += new_color; + if (millis() % 2) { + new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); + } else { + new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); + } break; } case White: - strip[pos] = CRGB::White; + new_color = CRGB::White; break; case Black: - strip[pos] = CRGB::Black; + new_color = CRGB::Black; break; + default: + // Unknown pen + return; } + + WS2812FX::set_crgb(pos, new_color); } }; @@ -182,55 +185,57 @@ void addParticle(Particle *particle) { particles[numParticles++] = particle; } -void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawFlash(Particle *particle, WS2812FX* leds) { + auto num_leds = leds->getLengthTotal(); uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); for (int pos = 0; pos < num_leds; pos++) { - particle->draw_with_pen(strip, pos, c); + particle->draw_with_pen(leds, pos, c); } } -void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawPoint(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); - particle->draw_with_pen(strip, pos, c); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); + particle->draw_with_pen(leds, pos, c); } -void drawRadius(Particle *particle, CRGB strip[], uint8_t num_leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { +void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; nscale8(&c, 1, bright); uint8_t y = pos - i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(strip, y, c); + particle->draw_with_pen(leds, y, c); if (i == 0) continue; y = pos + i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(strip, y, c); + particle->draw_with_pen(leds, y, c); } } -void drawPop(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawPop(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); uint8_t radius = scale16((sin16(age_frac/2) - 32768) * 2, 8); - drawRadius(particle, strip, num_leds, pos, radius, c); + drawRadius(particle, leds, pos, radius, c); } -void drawBeatbox(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawBeatbox(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); uint8_t radius = 5; - drawRadius(particle, strip, num_leds, pos, radius, c, false); + drawRadius(particle, leds, pos, radius, c, false); } diff --git a/wled00/FX.h b/wled00/FX.h index 52a0ea3284..aede71dbce 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -905,6 +905,12 @@ class WS2812FX { // 96 bytes static WS2812FX* get_strip() { return instance; } + static CRGB get_crgb(uint32_t pos) { + return instance->col_to_crgb(instance->getPixelColor(pos)); + } + static void set_crgb(uint32_t pos, CRGB color) { + instance->setPixelColor(pos, instance->crgb_to_col(color)); + } }; From e90adb5ee69a4f97258ce99c5e8fb5e48af479ab Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 8 Aug 2022 04:40:07 -0400 Subject: [PATCH 050/263] Update node mesh code for less chattiness and quicker startup --- usermods/Tubes/controller.h | 25 ++- usermods/Tubes/global_state.h | 1 - usermods/Tubes/node.h | 291 +++++++++++++++++++++------------- 3 files changed, 194 insertions(+), 123 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index bde3b66906..51db4d45f5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -412,9 +412,7 @@ class PatternController : public MessageReceiver { addFlash(CRGB::Green); } - virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { - Serial.printf("From %03X/%03X: ", header->id, header->uplinkId); - + virtual void onCommand(CommandId command, void *data) { switch (command) { case COMMAND_RESET: Serial.println(F("reset")); @@ -433,22 +431,17 @@ class PatternController : public MessageReceiver { return; } - case COMMAND_NEXT: { - Serial.print(F(" next ")); - - memcpy(&this->next_state, data, sizeof(TubeState)); - this->next_state.print(); - Serial.println(F(" (obeying)")); - return; - } - case COMMAND_UPDATE: { Serial.print(F(" update ")); + auto update_data = (NodeUpdate*)data; + TubeState state; - memcpy(&state, data, sizeof(TubeState)); + memcpy(&state, &update_data->current, sizeof(TubeState)); + memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); state.print(); - Serial.println(F(" (obeying)")); + this->next_state.print(); + Serial.println(); // Catch up to this state this->load_pattern(state); @@ -460,7 +453,7 @@ class PatternController : public MessageReceiver { } Serial.print(F("UNKNOWN ")); - Serial.print(command, HEX); + Serial.println(command, HEX); } void read_keys() { @@ -625,7 +618,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->node->broadcast(this->current_state, this->next_state); + this->node->update_status(this->current_state, this->next_state); } }; diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index d75b940f83..bf4ba4f2e5 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,7 +57,6 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_NEXT = 0x30; const static CommandId COMMAND_RESET = 0xF0; const static CommandId COMMAND_BRIGHTNESS = 0x80; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 139bf1407a..f5d0d32309 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -14,12 +14,15 @@ #include "global_state.h" +// #define NODE_DEBUGGING +#define TESTING_NODE_ID 100 + #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define BROADCAST_RATE 3000 // Rate at which to broadcast as leader -#define REBROADCAST_RATE 7000 // Rate at which to re-broadcast as follower +#define BROADCAST_RATE 2000 // Rate at which to broadcast state updates as leader #define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost +#define REBROADCAST_TIME 15000 // Time at which followers are presumed re-uplinked typedef uint16_t MeshId; @@ -30,9 +33,16 @@ typedef struct { } MeshNodeHeader; typedef struct { - MeshNodeHeader header; TubeState current; TubeState next; +} NodeUpdate; + +typedef struct { + MeshNodeHeader header; + CommandId command; + union { + NodeUpdate update; + } data; } NodeMessage; void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); @@ -40,15 +50,16 @@ void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rs class MessageReceiver { public: - virtual void onCommand(CommandId command, MeshNodeHeader* header, void *data) { + virtual void onCommand(CommandId command, void *data) { // Abstract: subclasses must define } }; - -#define NODE_STATUS_INIT 0 -#define NODE_STATUS_BROADCASTING 1 -#define NODE_STATUS_QUIET 2 +typedef enum{ + NODE_STATUS_QUIET=0, + NODE_STATUS_STARTING=1, + NODE_STATUS_STARTED=2, +} NodeStatus; class LightNode { @@ -57,14 +68,15 @@ class LightNode { MessageReceiver *receiver; MeshNodeHeader header; + NodeStatus status = NODE_STATUS_QUIET; - char node_name[20]; + bool meshStarted = false; - uint8_t status = NODE_STATUS_INIT; - bool meshChanged = false; + char node_name[20]; - Timer uplinkTimer; - Timer broadcastTimer; + Timer uplinkTimer; // When this timer ends, assume uplink is lost. + Timer broadcastTimer; // When this timer ends, send a status update + Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink LightNode(MessageReceiver *receiver) { LightNode::instance = this; @@ -80,25 +92,26 @@ class LightNode { } void onWifiConnect() { - if (this->status == NODE_STATUS_BROADCASTING) { - Serial.println("Stop Broadcasting"); + if (this->meshStarted) { + Serial.println("WiFi connected: stop broadcasting"); quickEspNow.stop(); + this->meshStarted = false; } - - Serial.println("Stop broadcasting"); + this->status = NODE_STATUS_QUIET; } void onWifiDisconnect() { - if (this->status == NODE_STATUS_BROADCASTING) - return; - - WiFi.mode (WIFI_MODE_STA); - WiFi.disconnect(false, true); - quickEspNow.begin(1, WIFI_IF_STA); + if (!this->meshStarted) { + Serial.println("WiFi disconnected: start broadcasting"); + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + this->meshStarted = true; + } - Serial.println("Broadcasting"); - this->status = NODE_STATUS_BROADCASTING; + if (this->status == NODE_STATUS_QUIET) + this->status = NODE_STATUS_STARTING; } void onMeshChange() { @@ -109,147 +122,213 @@ class LightNode { ); } + void onPeerPing(MeshNodeHeader* node) { + // When receiving a message, if the IDs match, it's a conflict + // Reset to create a new ID. + if (node->id == this->header.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + // If the message arrives from a higher ID, switch into follower mode + if (node->id > this->header.uplinkId && node->id > this->header.id) { + if (this->header.id != TESTING_NODE_ID || node->id < 0x800) + this->follow(node); + } + + // If the message arrived from our uplink, track that we're still linked. + if (node->id == this->header.uplinkId) { + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + + // If a message indicates that another node is following this one, + // enter or continue re-broadcasting mode (unless already LEAD) + if (node->uplinkId == this->header.id) { + Serial.printf("%03X/%03X is following me\n", node->id, node->uplinkId); + this->rebroadcastTimer.start(REBROADCAST_TIME); + } + } + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - // Ignore this packet if it couldn't be a mesh report. + // Ignore this message if it isn't a valid message payload. if (len != sizeof(NodeMessage)) return; NodeMessage* message = (NodeMessage*)data; - // Serial.printf(">> Received %db ", len); - // Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); - // Serial.printf("at " MACSTR, MAC2STR(address)); - // Serial.printf("@ %ddBm: ", rssi); - - // Ignore this packet if wrong version +#ifdef NODE_DEBUGGING + Serial.printf(">> Received %db ", len); + Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + Serial.printf("at " MACSTR, MAC2STR(address)); + Serial.printf("@ %ddBm: ", rssi); +#endif + + // Ignore this message if it's the wrong version. if (message->header.version != this->header.version) { - // Serial.printf(" header.id <= this->header.uplinkId) { - // Serial.printf(" ignoring\n", rssi); + // Track that another node exists, updating this node's understanding of the mesh. + this->onPeerPing(&message->header); + + // Ignore this message if not from my uplink + if (message->header.id != this->header.uplinkId) { +#ifdef NODE_DEBUGGING + Serial.printf(" ignoring\n"); +#endif return; } - // Serial.printf(" listening\n", rssi); - this->onPeerPing(&message->header); +#ifdef NODE_DEBUGGING + Serial.printf(" listening\n"); +#endif // Execute the received command - MeshId fromId = message->header.uplinkId; - if (!fromId) fromId = message->header.id; - - this->receiver->onCommand( - COMMAND_UPDATE, - &message->header, - &message->current - ); + Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); this->receiver->onCommand( - COMMAND_NEXT, - &message->header, - &message->next + message->command, + &message->data ); - } - void onPeerPing(MeshNodeHeader* node) { - if (node->id == this->header.id) { - Serial.println("Detected an ID conflict."); - this->reset(); - } - - if (node->id > this->header.uplinkId && node->id > this->header.id) { - Serial.printf("Following %03X:%03X\n", - node->id, - node->uplinkId - ); - - this->follow(node->id); + // Re-broadcast the message if appropriate + if (!this->rebroadcastTimer.ended()) { + message->header = this->header; + this->broadcast(message, true); } + } - if (node->id == this->header.uplinkId) { - this->uplinkTimer.start(UPLINK_TIMEOUT); - } + void broadcast(NodeMessage *message, bool is_rebroadcast=false) { + // Don't broadcast anything if this node isn't active. + if (this->status != NODE_STATUS_STARTED) + return; + + auto err = quickEspNow.send( + ESPNOW_BROADCAST_ADDRESS, + (uint8_t*)message, sizeof(*message) + ); + if (err) + Serial.printf(">> Broadcast error %d\n", err); } void setup() { configure_ap(); - this->broadcastTimer.start(BROADCAST_RATE); - this->status = NODE_STATUS_INIT; - +#ifdef NODE_DEBUGGING + this->reset(TESTING_NODE_ID); +#else this->reset(); - this->onMeshChange(); +#endif + this->broadcastTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); Serial.println("Node: ok"); } - void broadcast(TubeState ¤t, TubeState &next) { - // Don't broadcast if not in broadcast mode - if (this->status != NODE_STATUS_BROADCASTING) + void set_timer() { + // Timer in QUIET mode determines how often we'll check WiFi status + if (this->status == NODE_STATUS_QUIET) { + this->broadcastTimer.start(BROADCAST_RATE); + this->rebroadcastTimer.stop(); return; + } - NodeMessage message = { - .header = this->header, - .current = current, - .next = next, - }; - - auto err = quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, - (uint8_t*)&message, sizeof(message)); - if (err) - Serial.printf(">> Broadcast error %d\n", err); + // Initial timer: wait for a bit before trying to broadcast. + // If this node's ID is high, it's more likely to be the leader, so wait less. + if (this->status == NODE_STATUS_STARTING) { + auto next_time = 4000 - this->header.id/2; + this->broadcastTimer.start(next_time); + this->rebroadcastTimer.start(REBROADCAST_TIME); + return; + } + // If following, only rebroadcast every 5 cycles if (this->is_following()) { - this->broadcastTimer.snooze(REBROADCAST_RATE); - } else { - this->broadcastTimer.snooze(BROADCAST_RATE); + auto next_time = 5 * BROADCAST_RATE; + + // Randomize a bit so not everyone is rebroadcasting at the same time + next_time += random(0, 2000) - 1000; + + this->broadcastTimer.start(next_time); + return; } + + this->broadcastTimer.start(BROADCAST_RATE); } void update(TubeState ¤t, TubeState &next) { - // Don't do anything for the first second, to allow Wifi to settle - if (millis() < 1000) - return; - // Check the last time we heard from the uplink node if (is_following() && this->uplinkTimer.ended()) { - Serial.println("Uplink lost"); - this->follow(0); - } - - if (this->meshChanged) { - this->onMeshChange(); - this->meshChanged = false; + this->follow(NULL); } if (this->broadcastTimer.ended()) { + // The broadcast timer doubles as a timer for startup delay + // Once the initial timer has ended, mark this node as started + if (this->status == NODE_STATUS_STARTING) + this->status = NODE_STATUS_STARTED; + + // Check WiFi status and update node status if wifi changed if (WiFi.isConnected()) this->onWifiConnect(); else this->onWifiDisconnect(); - this->broadcast(current, next); + // Set the next time and reset the rebroadcast monitoring + this->update_status(current, next); + this->set_timer(); } } + void update_status(TubeState ¤t, TubeState &next) { + // Broadcast (or rebroadcast) the current state + NodeMessage message = { + .header = this->header, + .command = COMMAND_UPDATE, + .data = { + .update = { + .current = current, + .next = next + } + } + }; + this->broadcast(&message); + } + void reset(MeshId id = 0) { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; - this->follow(0); - this->meshChanged = true; + this->status = NODE_STATUS_STARTING; + this->follow(NULL); + this->onMeshChange(); } - void follow(MeshId uplinkId) { - // Update uplink ID - if (this->header.uplinkId == uplinkId) + void follow(MeshNodeHeader* node) { + if (node == NULL) { + if (this->header.uplinkId != 0) { + Serial.println("Uplink lost"); + } + + // Unfollow: following zero means you have no uplink + this->header.uplinkId = 0; + this->onMeshChange(); + return; + } + + // Already following? ignore + if (this->header.uplinkId == node->id) return; - // Following zero means you have no uplink - this->header.uplinkId = uplinkId; - this->meshChanged = true; + // Follow + Serial.printf("Following %03X:%03X\n", + node->id, + node->uplinkId + ); + this->header.uplinkId = node->id; + this->onMeshChange(); } bool is_following() { From c7b3e1b64d9a2843fd83afdf0d1bc7f138f26017 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 9 Aug 2022 12:10:38 -0400 Subject: [PATCH 051/263] fix post merge --- usermods/Tubes/particle.h | 17 +++++++++-------- wled00/FX.cpp | 10 ++++++++-- wled00/FX.h | 13 ++++--------- wled00/const.h | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index bbe6118475..d1afb6a69d 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -97,6 +97,7 @@ class Particle { } void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { + CRGB c = CRGB(WS2812FX::get_strip()->getPixelColor(pos)); CRGB new_color; switch (this->pen) { @@ -105,35 +106,35 @@ class Particle { break; case Blend: - new_color = WS2812FX::get_crgb(pos) | color; + new_color = c | color; break; case Erase: - new_color = WS2812FX::get_crgb(pos) & color; + new_color = c & color; break; case Invert: - new_color = -WS2812FX::get_crgb(pos); + new_color = -c; break; case Brighten: { uint8_t t = color.getAverageLight(); - new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); + new_color = c + CRGB(t,t,t); break; } case Darken: { uint8_t t = color.getAverageLight(); - new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); + new_color = c - CRGB(t,t,t); break; } case Flicker: { uint8_t t = color.getAverageLight(); if (millis() % 2) { - new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); + new_color = c - CRGB(t,t,t); } else { - new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); + new_color = c + CRGB(t,t,t); } break; } @@ -151,7 +152,7 @@ class Particle { return; } - WS2812FX::set_crgb(pos, new_color); + WS2812FX::get_strip()->setPixelColor(pos, new_color); } }; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 39a10e0aa4..8eab39cc59 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5279,7 +5279,7 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;!,!,!;!;2d"; -uint16_t WS2812FX::mode_external(void) { +uint16_t mode_external(void) { // uint8_t segment_id = strip.getMainSegmentId(); uint16_t length = strip.getLengthTotal(); @@ -5291,7 +5291,7 @@ uint16_t WS2812FX::mode_external(void) { strip.setPixelColor(i, external_buffer[p]); } } -static const char _data_FX_MODE_TUBES_NOISE[] PROGMEM = "External!@!,;!,!,;!;1d"; +static const char _data_FX_MODE_EXTERNAL[] PROGMEM = "External!@!,;!,!,;!;1d"; //////////////////////////////// // 2D Polar Lights // @@ -7527,6 +7527,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_DJLIGHT, &mode_DJLight, _data_FX_MODE_DJLIGHT); addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); +<<<<<<< HEAD } @@ -7571,3 +7572,8 @@ uint16_t WS2812FX::mode_tubes_moise(void) { return FRAMETIME; } +======= + //addEffect(FX_MODE_CUSTOMEFFECT, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT); //WLEDSR Custom Effects +#endif // USERMOD_AUDIOREACTIVE +} +>>>>>>> 0a7756d3 (fix post merge) diff --git a/wled00/FX.h b/wled00/FX.h index aede71dbce..845d1335d6 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -888,12 +888,13 @@ class WS2812FX { // 96 bytes return instance->external_buffer; } static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { - uint32_t color = instance->color_from_palette(c, false, true, 255, brightness); - return instance->col_to_crgb(color); + Segment& segment = instance->getMainSegment(); + uint32_t color = segment.color_from_palette(c, false, true, 255, brightness); + return CRGB(color); } static void load_palette(uint8_t palette_id) { for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { - WS2812FX::Segment& seg = instance->getSegment(i); + Segment& seg = instance->getSegment(i); seg.palette = palette_id; } } @@ -905,12 +906,6 @@ class WS2812FX { // 96 bytes static WS2812FX* get_strip() { return instance; } - static CRGB get_crgb(uint32_t pos) { - return instance->col_to_crgb(instance->getPixelColor(pos)); - } - static void set_crgb(uint32_t pos, CRGB color) { - instance->setPixelColor(pos, instance->crgb_to_col(color)); - } }; diff --git a/wled00/const.h b/wled00/const.h index a04a31c802..1ed50a0a2f 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 58 +#define GRADIENT_PALETTE_COUNT 114 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" From a6e82f58de75c5363e6b5030d662ee77dcfa277f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 9 Aug 2022 12:36:52 -0400 Subject: [PATCH 052/263] more fix after merge --- wled00/FX.cpp | 4 +++- wled00/FX_fcn.cpp | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 8eab39cc59..465c873585 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5282,14 +5282,16 @@ static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fad uint16_t mode_external(void) { // uint8_t segment_id = strip.getMainSegmentId(); uint16_t length = strip.getLengthTotal(); + auto external_buffer = strip.get_external_buffer(); for (int i = 0, p = 0; i < length; i++, p++) { if (p >= EXTERNAL_BUFFER_SIZE) { p = 0; } - // strip.setPixelColor(i, color_from_palette(external_buffer[p], true, PALETTE_SOLID_WRAP, 0)); strip.setPixelColor(i, external_buffer[p]); } + + return FRAMETIME; } static const char _data_FX_MODE_EXTERNAL[] PROGMEM = "External!@!,;!,!,;!;1d"; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 56ab09c371..d13e5a5c32 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1549,11 +1549,17 @@ uint8_t Bus::_gAWM = 255; const char JSON_mode_names[] PROGMEM = R"=====(["Mode names have moved"])====="; const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", -"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", -"Pastel","Sunset 2","Beach","Vintage","Departure","Landscape","Beech","Sherbet","Hult","Hult 64", -"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", -"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", -"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", -"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", -"Candy2" +"Forest","Rainbow","Rainbow Bands","July","Vintage 57","Vintage 01","Rivendell","RGI 15","Retro","Analogous", +"Pink Splash 08","Pink Splash 07","Coral Reef","Ocean Breeze 68","Ocean Breeze 36","Departure","Landscape 64","Landscape 33","Sherbet","Hult 65", +"Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Lava","Fire","Hiyane","Colorfull","Magenta Evening", +"Pink Purple","Sunset","Autumn","B/B/M/W","B/M/R","B/R/M/Y","Sunset Yellow","Cloud","Fire & Ice","BHW2", +"Rainfall","Angel","Butterfly","250K Meters","Midnight","Afterdusk","Blue Sky","Gold Orange","Frizell 05","Frizell 09", +"Frizell 10","Frizell 12","Fib 01","Fib 18","Fib 07","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a", +"Cyan Orange","C/W/G","Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple", +"Knoza 00","Knoza 18","Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konjo 18","Konjo 19", +"Konikyo","McCahon","Sulz 10","Sulz 12","Sulz 15","Sulz 21","Sulz 22","Pills","Pink/Yellow/Orange","Autumn 04", +"Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10","Landscape 76", +"Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10","Vintage 56", +"Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" ])====="; + From 105aaedc4163b3f5feb925eff7d349d331a122f7 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 9 Aug 2022 14:10:20 -0400 Subject: [PATCH 053/263] More fixes after merge, update palettes --- usermods/Tubes/debug.h | 16 +++++++++++++++- usermods/Tubes/node.h | 4 ++-- wled00/FX_fcn.cpp | 17 ++++++++--------- wled00/const.h | 2 +- wled00/palettes.h | 13 +------------ 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 28a8397526..1d65231e9e 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -41,11 +41,25 @@ class DebugController { this->lastFrame = (uint32_t)-1; } + std::string status_code(NodeStatus status) { + switch (status) { + case NODE_STATUS_QUIET: + return std::string(" (quiet)"); + case NODE_STATUS_STARTING: + return std::string(" (starting)"); + case NODE_STATUS_STARTED: + return std::string(""); + default: + return std::string("??"); + } + } + void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", + Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", this->controller->node->node_name, + status_code(this->controller->node->status).c_str(), WiFi.status(), WiFi.channel(), WiFi.localIP()[0], diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f5d0d32309..cd5ca6a3d7 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -87,8 +87,8 @@ class LightNode { void configure_ap() { strcpy(clientSSID, ""); strcpy(clientPass, ""); - strcpy(apSSID, ""); - apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; + strcpy(apSSID, "mywled"); + apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; } void onWifiConnect() { diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index d13e5a5c32..34900882d1 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1552,14 +1552,13 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "Forest","Rainbow","Rainbow Bands","July","Vintage 57","Vintage 01","Rivendell","RGI 15","Retro","Analogous", "Pink Splash 08","Pink Splash 07","Coral Reef","Ocean Breeze 68","Ocean Breeze 36","Departure","Landscape 64","Landscape 33","Sherbet","Hult 65", "Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Lava","Fire","Hiyane","Colorfull","Magenta Evening", -"Pink Purple","Sunset","Autumn","B/B/M/W","B/M/R","B/R/M/Y","Sunset Yellow","Cloud","Fire & Ice","BHW2", -"Rainfall","Angel","Butterfly","250K Meters","Midnight","Afterdusk","Blue Sky","Gold Orange","Frizell 05","Frizell 09", -"Frizell 10","Frizell 12","Fib 01","Fib 18","Fib 07","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a", -"Cyan Orange","C/W/G","Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple", -"Knoza 00","Knoza 18","Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konjo 18","Konjo 19", -"Konikyo","McCahon","Sulz 10","Sulz 12","Sulz 15","Sulz 21","Sulz 22","Pills","Pink/Yellow/Orange","Autumn 04", -"Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10","Landscape 76", -"Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10","Vintage 56", -"Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" +"Pink Purple","Sunset","Autumn","Blue/Magenta/White","Blue/Magenta/Red","Blue/Red/Yellow","Blue/Cyan/Yellow","Sunset Yellow","Cloud","Fire & Ice", +"BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", +"Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", +"Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18", +"Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", +"Autumn 04","Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10", +"Landscape 76","Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10", +"Vintage 56","Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" ])====="; diff --git a/wled00/const.h b/wled00/const.h index 1ed50a0a2f..2cbaa10e61 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 114 // custom palette.h +#define GRADIENT_PALETTE_COUNT 103 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" diff --git a/wled00/palettes.h b/wled00/palettes.h index 5bb34f13e7..c2ab9295bf 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -2019,14 +2019,10 @@ const byte* const gGradientPalettes[] PROGMEM = { Afterdusk_gp, BlueSky_gp, Gold_Orange_gp, - frizzell_05_gp, - frizzell_09_gp, frizzell_10_gp, frizzell_12_gp, - fib53_01_gp, fib53_18_gp, - fib53_07_gp, fib53_13_gp, fib53_17_gp, fib53_05_gp, @@ -2052,15 +2048,8 @@ const byte* const gGradientPalettes[] PROGMEM = { grindylow_15_gp, grindylow_21_gp, konjo_08_gp, - konjo_18_gp, - konjo_19_gp, konkikyo_19_gp, mccahon_16_gp, - sulz_10_gp, - sulz_12_gp, - sulz_15_gp, - sulz_21_gp, - sulz_22_gp, Pills_2_gp, Pink_Yellow_Orange_1_gp, es_autumn_04_gp, @@ -2153,7 +2142,7 @@ const byte* const gGradientPalettes[] PROGMEM = { */ }; const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); -#define GRADIENT_PALETTE_COUNT 114 +#define GRADIENT_PALETTE_COUNT 103 #endif From c643c64cc7c12b9d9dce5791539640904ce8366f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 00:50:50 -0400 Subject: [PATCH 054/263] Handle manual override of colors --- usermods/Tubes/controller.h | 19 ++++++++-- wled00/FX.h | 74 ++++++++++++++++++++++++++++++++++++- wled00/FX_fcn.cpp | 2 +- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 51db4d45f5..ea7a3dc342 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -130,14 +130,24 @@ class PatternController : public MessageReceiver { void update() { - this->node->update(this->current_state, this->next_state); - this->read_keys(); // Update patterns to the beat this->update_beat(); - uint16_t phrase = this->current_state.beat_frame >> 12; + + // Detect manual overrides & update the current state to match. + Segment& segment = WS2812FX::get_strip()->getMainSegment(); + if (segment.palette != this->current_state.palette_id) { + Serial.printf("Palette override = %d\n",segment.palette); + this->next_state.palette_id = segment.palette; + this->next_state.palette_phrase = phrase; + this->updateTimer.stop(); + } + // if (segment.mode != FX_MODE_EXTERNAL) { + // Serial.printf("Pattern override = %d\n",segment.mode); + // } + if (phrase >= this->next_state.pattern_phrase) { this->load_pattern(this->next_state); this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); @@ -151,6 +161,9 @@ class PatternController : public MessageReceiver { this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); } + // Update the mesh + this->node->update(this->current_state, this->next_state); + // Update current status if (this->updateTimer.ended()) { this->send_update(); diff --git a/wled00/FX.h b/wled00/FX.h index 845d1335d6..a0c971690a 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -275,6 +275,7 @@ #define FX_MODE_2DMETABALLS 142 // non audio #define FX_MODE_2DPULSER 143 // non audio #define FX_MODE_2DDRIFT 144 // non audio +<<<<<<< HEAD #define FX_MODE_2DWAVERLY 145 // audio enhanced #define FX_MODE_2DSWIRL 146 // audio enhanced #define FX_MODE_2DAKEMI 147 // audio enhanced @@ -308,6 +309,68 @@ #define FX_MODE_ROCKTAVES 174 // audio enhanced #define MODE_COUNT 175 +======= +#endif +#ifndef WLED_DISABLE_AUDIO + #ifndef WLED_DISABLE_2D + #define FX_MODE_2DWAVERLY 145 // audio enhanced + #define FX_MODE_2DSWIRL 146 // audio enhanced + #define FX_MODE_2DAKEMI 147 // audio enhanced + // 148 & 149 reserved + #endif + #define FX_MODE_PIXELWAVE 150 // audio enhanced + #define FX_MODE_JUGGLES 151 // audio enhanced + #define FX_MODE_MATRIPIX 152 // audio enhanced + #define FX_MODE_GRAVIMETER 153 // audio enhanced + #define FX_MODE_PLASMOID 154 // audio enhanced + #define FX_MODE_PUDDLES 155 // audio enhanced + #define FX_MODE_MIDNOISE 156 // audio enhanced + #define FX_MODE_NOISEMETER 157 // audio enhanced + #define FX_MODE_NOISEFIRE 158 // audio enhanced + #define FX_MODE_PUDDLEPEAK 159 // audio enhanced + #define FX_MODE_RIPPLEPEAK 160 // audio enhanced + #define FX_MODE_GRAVCENTER 161 // audio enhanced + #define FX_MODE_GRAVCENTRIC 162 // audio enhanced +#endif + +#ifndef USERMOD_AUDIOREACTIVE + #ifndef WLED_DISABLE_AUDIO + #define MODE_COUNT 163 + #else + #ifndef WLED_DISABLE_2D + #define MODE_COUNT 145 + #else + #define MODE_COUNT 118 + #endif + #endif + +#else + + #ifdef WLED_DISABLE_AUDIO + #error Incompatible options: WLED_DISABLE_AUDIO and USERMOD_AUDIOREACTIVE + #endif + #ifdef WLED_DISABLE_2D + #error AUDIOREACTIVE usermod requires 2D support. + #endif + #define FX_MODE_2DGEQ 148 + #define FX_MODE_2DFUNKYPLANK 149 + #define FX_MODE_PIXELS 163 + #define FX_MODE_FREQWAVE 164 + #define FX_MODE_FREQMATRIX 165 + #define FX_MODE_WATERFALL 166 + #define FX_MODE_FREQPIXELS 167 + #define FX_MODE_BINMAP 168 + #define FX_MODE_NOISEMOVE 169 + #define FX_MODE_FREQMAP 170 + #define FX_MODE_GRAVFREQ 171 + #define FX_MODE_DJLIGHT 172 + #define FX_MODE_BLURZ 173 + #define FX_MODE_ROCKTAVES 174 + //#define FX_MODE_CUSTOMEFFECT 175 //WLEDSR Custom Effects + + #define MODE_COUNT 175 +#endif +>>>>>>> d4cc6df2 (Handle manual override of colors) typedef enum mapping1D2D { M12_Pixels = 0, @@ -893,13 +956,20 @@ class WS2812FX { // 96 bytes return CRGB(color); } static void load_palette(uint8_t palette_id) { - for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { Segment& seg = instance->getSegment(i); + if (!seg.isActive()) continue; + if (instance->paletteBlend) + seg.startTransition(instance->getTransition()); seg.palette = palette_id; } } static void load_pattern(uint8_t pattern_id) { - for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { + Segment& seg = instance->getSegment(i); + if (!seg.isActive()) continue; + if (instance->paletteBlend) + seg.startTransition(instance->getTransition()); instance->setMode(i, pattern_id); } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 34900882d1..484e6019c9 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1551,7 +1551,7 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", "Forest","Rainbow","Rainbow Bands","July","Vintage 57","Vintage 01","Rivendell","RGI 15","Retro","Analogous", "Pink Splash 08","Pink Splash 07","Coral Reef","Ocean Breeze 68","Ocean Breeze 36","Departure","Landscape 64","Landscape 33","Sherbet","Hult 65", -"Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Lava","Fire","Hiyane","Colorfull","Magenta Evening", +"Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Hot Lava","Fire","Hiyane","Colorfull","Magenta Evening", "Pink Purple","Sunset","Autumn","Blue/Magenta/White","Blue/Magenta/Red","Blue/Red/Yellow","Blue/Cyan/Yellow","Sunset Yellow","Cloud","Fire & Ice", "BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", "Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", From e80840cce8f2a4bcea4aef7adbae200ae16a6639 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 01:42:33 -0400 Subject: [PATCH 055/263] Rework mesh sync code to pull message sending up into controller --- usermods/Tubes/controller.h | 21 +++++---- usermods/Tubes/node.h | 91 ++++++++++++------------------------- 2 files changed, 42 insertions(+), 70 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ea7a3dc342..c900bf5f83 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -131,6 +131,9 @@ class PatternController : public MessageReceiver { void update() { this->read_keys(); + + // Update the mesh + this->node->update(); // Update patterns to the beat this->update_beat(); @@ -161,14 +164,14 @@ class PatternController : public MessageReceiver { this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); } - // Update the mesh - this->node->update(this->current_state, this->next_state); - // Update current status - if (this->updateTimer.ended()) { - this->send_update(); - this->updateTimer.snooze(STATUS_UPDATE_PERIOD); - } + if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { + // Transmit less often when following + if (!this->node->is_following() || random(0, 4) == 0) { + this->send_update(); + } + + } if (this->graphicsTimer.every(REFRESH_PERIOD)) { this->updateGraphics(); @@ -222,8 +225,6 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - // this->node->update_node_storage(this->current_state, this->next_state); - uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); Serial.print(this->next_state.pattern_phrase - phrase); @@ -235,6 +236,8 @@ class PatternController : public MessageReceiver { this->next_state.print(); Serial.print(F(" ")); Serial.println(); + + this->node->update_status(this->current_state, this->next_state); } void background_changed() { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index cd5ca6a3d7..0c863d072a 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -20,9 +20,9 @@ #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define BROADCAST_RATE 2000 // Rate at which to broadcast state updates as leader #define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost #define REBROADCAST_TIME 15000 // Time at which followers are presumed re-uplinked +#define WIFI_CHECK_RATE 2000 // Time at which we should check wifi status again typedef uint16_t MeshId; @@ -70,12 +70,10 @@ class LightNode { MeshNodeHeader header; NodeStatus status = NODE_STATUS_QUIET; - bool meshStarted = false; - char node_name[20]; + Timer statusTimer; // Use this timer to initialize and check wifi status Timer uplinkTimer; // When this timer ends, assume uplink is lost. - Timer broadcastTimer; // When this timer ends, send a status update Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink LightNode(MessageReceiver *receiver) { @@ -88,30 +86,30 @@ class LightNode { strcpy(clientSSID, ""); strcpy(clientPass, ""); strcpy(apSSID, "mywled"); + strcpy(apPass, "WledWled"); apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; } void onWifiConnect() { - if (this->meshStarted) { - Serial.println("WiFi connected: stop broadcasting"); - quickEspNow.stop(); - this->meshStarted = false; - } - + if (this->status == NODE_STATUS_QUIET) + return; + + Serial.println("WiFi connected: stop broadcasting"); + quickEspNow.stop(); this->status = NODE_STATUS_QUIET; + this->rebroadcastTimer.stop(); + this->statusTimer.start(WIFI_CHECK_RATE); } void onWifiDisconnect() { - if (!this->meshStarted) { - Serial.println("WiFi disconnected: start broadcasting"); - WiFi.mode (WIFI_MODE_STA); - WiFi.disconnect(false, true); - quickEspNow.begin(1, WIFI_IF_STA); - this->meshStarted = true; - } + if (this->status != NODE_STATUS_QUIET) + return; - if (this->status == NODE_STATUS_QUIET) - this->status = NODE_STATUS_STARTING; + Serial.println("WiFi disconnected: start broadcasting"); + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + this->initialize(); } void onMeshChange() { @@ -141,9 +139,11 @@ class LightNode { this->uplinkTimer.start(UPLINK_TIMEOUT); } - // If a message indicates that another node is following this one, - // enter or continue re-broadcasting mode (unless already LEAD) - if (node->uplinkId == this->header.id) { + // If a message indicates that another node is following this one, or + // should be (it's not following anything, but this node's ID is higher) + // enter or continue re-broadcasting mode. + if (node->uplinkId == this->header.id + || (node->uplinkId == 0 && node->id < this->header.id)) { Serial.printf("%03X/%03X is following me\n", node->id, node->uplinkId); this->rebroadcastTimer.start(REBROADCAST_TIME); } @@ -220,51 +220,26 @@ class LightNode { #else this->reset(); #endif - this->broadcastTimer.stop(); - + this->statusTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); - Serial.println("Node: ok"); } - void set_timer() { - // Timer in QUIET mode determines how often we'll check WiFi status - if (this->status == NODE_STATUS_QUIET) { - this->broadcastTimer.start(BROADCAST_RATE); - this->rebroadcastTimer.stop(); - return; - } - - // Initial timer: wait for a bit before trying to broadcast. + void initialize() { + // Initialization timer: wait for a bit before trying to broadcast. // If this node's ID is high, it's more likely to be the leader, so wait less. - if (this->status == NODE_STATUS_STARTING) { - auto next_time = 4000 - this->header.id/2; - this->broadcastTimer.start(next_time); - this->rebroadcastTimer.start(REBROADCAST_TIME); - return; - } - - // If following, only rebroadcast every 5 cycles - if (this->is_following()) { - auto next_time = 5 * BROADCAST_RATE; - - // Randomize a bit so not everyone is rebroadcasting at the same time - next_time += random(0, 2000) - 1000; - - this->broadcastTimer.start(next_time); - return; - } - - this->broadcastTimer.start(BROADCAST_RATE); + this->status = NODE_STATUS_STARTING; + this->statusTimer.start(3000 - this->header.id / 2); + this->rebroadcastTimer.stop(); } - void update(TubeState ¤t, TubeState &next) { + void update() { // Check the last time we heard from the uplink node if (is_following() && this->uplinkTimer.ended()) { this->follow(NULL); } - if (this->broadcastTimer.ended()) { + if (this->statusTimer.every(WIFI_CHECK_RATE)) { // The broadcast timer doubles as a timer for startup delay // Once the initial timer has ended, mark this node as started if (this->status == NODE_STATUS_STARTING) @@ -275,10 +250,6 @@ class LightNode { this->onWifiConnect(); else this->onWifiDisconnect(); - - // Set the next time and reset the rebroadcast monitoring - this->update_status(current, next); - this->set_timer(); } } @@ -301,9 +272,7 @@ class LightNode { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; - this->status = NODE_STATUS_STARTING; this->follow(NULL); - this->onMeshChange(); } void follow(MeshNodeHeader* node) { From 8224be18dc609f178e5fbb43e1fa48751f84fbb0 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 03:21:19 -0400 Subject: [PATCH 056/263] misc utils --- usermods/Tubes/Tubes.h | 1 + usermods/Tubes/debug.h | 4 +++- usermods/Tubes/node.h | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 81aebeab47..ff12ae99fc 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -30,6 +30,7 @@ class TubesUsermod : public Usermod { void randomize() { randomSeed(esp_random()); + random16_set_seed(random(0, 65535)); random16_add_entropy(esp_random()); } diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 1d65231e9e..9062358ee6 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -57,7 +57,7 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", + Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n\n", this->controller->node->node_name, status_code(this->controller->node->status).c_str(), WiFi.status(), @@ -67,6 +67,8 @@ class DebugController { WiFi.localIP()[2], WiFi.localIP()[3], freeMemory(), + LITTLEFS.usedBytes(), + LITTLEFS.totalBytes(), formatted_time(millis()).c_str() ); } diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 0c863d072a..c87a697a1e 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -85,8 +85,9 @@ class LightNode { void configure_ap() { strcpy(clientSSID, ""); strcpy(clientPass, ""); - strcpy(apSSID, "mywled"); + sprintf(apSSID, "WLED %03X", this->header.id); strcpy(apPass, "WledWled"); + apActive = !this->is_following(); apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; } @@ -118,6 +119,7 @@ class LightNode { this->header.id, this->header.uplinkId ); + this->configure_ap(); } void onPeerPing(MeshNodeHeader* node) { From 7e3b1aa025e30e5f358567959d817c1345060e3e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 12:20:57 -0400 Subject: [PATCH 057/263] Fix palette fading --- usermods/Tubes/controller.h | 42 ++++++++++++++++++++++--------------- usermods/Tubes/node.h | 15 ++++++++++--- wled00/FX.h | 9 ++++---- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c900bf5f83..60eabb4018 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -12,7 +12,7 @@ #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -#define STATUS_UPDATE_PERIOD 1000 +#define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 @@ -126,6 +126,25 @@ class PatternController : public MessageReceiver { this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); + + WS2812FX::load_pattern(FX_MODE_EXTERNAL); + } + + void do_pattern_changes() { + uint16_t phrase = this->current_state.beat_frame >> 12; + + if (phrase >= this->next_state.pattern_phrase) { + this->load_pattern(this->next_state); + this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + } + if (phrase >= this->next_state.palette_phrase) { + this->load_palette(this->next_state); + this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + } + if (phrase >= this->next_state.effect_phrase) { + this->load_effect(this->next_state); + this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + } } void update() @@ -151,32 +170,21 @@ class PatternController : public MessageReceiver { // Serial.printf("Pattern override = %d\n",segment.mode); // } - if (phrase >= this->next_state.pattern_phrase) { - this->load_pattern(this->next_state); - this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); - } - if (phrase >= this->next_state.palette_phrase) { - this->load_palette(this->next_state); - this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); - } - if (phrase >= this->next_state.effect_phrase) { - this->load_effect(this->next_state); - this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + do_pattern_changes(); + + if (this->graphicsTimer.every(REFRESH_PERIOD)) { + this->updateGraphics(); } // Update current status if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!this->node->is_following() || random(0, 4) == 0) { + if (!this->node->is_following() || random(0, 5) == 0) { this->send_update(); } } - if (this->graphicsTimer.every(REFRESH_PERIOD)) { - this->updateGraphics(); - } - #ifdef USELCD if (this->lcd->active) { this->lcd->size(1); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index c87a697a1e..58f3f40b87 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -13,6 +13,7 @@ #include #include "global_state.h" +#include "wled.h" // #define NODE_DEBUGGING #define TESTING_NODE_ID 100 @@ -83,12 +84,20 @@ class LightNode { } void configure_ap() { + // Try to hide the access point unless this is the "root" node strcpy(clientSSID, ""); strcpy(clientPass, ""); - sprintf(apSSID, "WLED %03X", this->header.id); + if (this->is_following()) { + sprintf(apSSID, "WLED %03X", this->header.id); + } else { + sprintf(apSSID, "WLED %03X", this->header.id); + } strcpy(apPass, "WledWled"); - apActive = !this->is_following(); - apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; + apBehavior = AP_BEHAVIOR_NO_CONN; + + bootPreset = 0; // Try to prevent initial playlists from starting + fadeTransition = true; + transitionDelay = 6000; } void onWifiConnect() { diff --git a/wled00/FX.h b/wled00/FX.h index a0c971690a..3333941842 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -958,9 +958,9 @@ class WS2812FX { // 96 bytes static void load_palette(uint8_t palette_id) { for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { Segment& seg = instance->getSegment(i); + if (seg.palette == palette_id) continue; if (!seg.isActive()) continue; - if (instance->paletteBlend) - seg.startTransition(instance->getTransition()); + seg.startTransition(instance->getTransition()); seg.palette = palette_id; } } @@ -968,9 +968,10 @@ class WS2812FX { // 96 bytes for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { Segment& seg = instance->getSegment(i); if (!seg.isActive()) continue; - if (instance->paletteBlend) - seg.startTransition(instance->getTransition()); + if (seg.mode == pattern_id) continue; + seg.startTransition(instance->getTransition()); instance->setMode(i, pattern_id); + seg.markForReset(); } } static WS2812FX* get_strip() { From 08757fd6381145d34b4595ff23cc9f65b9056751 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 13:41:36 -0400 Subject: [PATCH 058/263] Start testing crossfading --- usermods/Tubes/Tubes.h | 31 +++++++++++++++++++++++++++++++ usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/node.h | 5 ----- usermods/Tubes/util.h | 2 ++ usermods/Tubes/virtual_strip.h | 2 +- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index ff12ae99fc..edb91ba0bc 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -38,6 +38,12 @@ class TubesUsermod : public Usermod { void setup() { randomize(); + // Override some behaviors on all Tubes + bootPreset = 0; // Try to prevent initial playlists from starting + fadeTransition = true; // Fade palette transitions + transitionDelay = 8000; // Fade them for a long time + WS2812FX::load_pattern(DEFAULT_WLED_FX); // Crossfade with FLOW + // Start timing globalTimer.setup(); beats.setup(); @@ -61,6 +67,31 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { + // Perform a cross-fade between current WLED mode and the external buffer + + // uint8_t segment_id = strip.getMainSegmentId(); + uint16_t length = strip.getLengthTotal(); + auto external_buffer = strip.get_external_buffer(); + + uint8_t fade = sin8(millis() / 40); // amount that Tubes overwrites WLED, 0-255 + + if (fade > 0) { + for (int i = 0, p = 0; i < length; i++, p++) { + if (p >= EXTERNAL_BUFFER_SIZE) { + p = 0; + } + + CRGB color1 = strip.getPixelColor(i); + CRGB color2 = external_buffer[p]; + + uint8_t r = blend8(color1.r, color2.r, fade); + uint8_t g = blend8(color1.g, color2.g, fade); + uint8_t b = blend8(color1.b, color2.b, fade); + + strip.setPixelColor(i, CRGB(r,g,b)); + } + } + // Draw effects layers over whatever WLED is doing. WS2812FX* leds = &strip; controller.effects->draw(leds); diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 58252d44d4..2ab09732f3 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -11,7 +11,7 @@ class LEDs { CRGB leds[MAX_REAL_LEDS]; // CRGB led_array[MAX_REAL_LEDS]; - const static int FRAMES_PER_SECOND = 300; // how often we refresh the strip, in frames per second + const static int FRAMES_PER_SECOND = 150; // how often we refresh the strip, in frames per second const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we refresh the strip, in milliseconds int num_leds; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 58f3f40b87..57a91568a9 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -13,7 +13,6 @@ #include #include "global_state.h" -#include "wled.h" // #define NODE_DEBUGGING #define TESTING_NODE_ID 100 @@ -94,10 +93,6 @@ class LightNode { } strcpy(apPass, "WledWled"); apBehavior = AP_BEHAVIOR_NO_CONN; - - bootPreset = 0; // Try to prevent initial playlists from starting - fadeTransition = true; - transitionDelay = 6000; } void onWifiConnect() { diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index 60371c52d5..e49f208697 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -5,6 +5,8 @@ // Is this the tube that can control others? #define MASTER_TUBE +#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS + uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { uint16_t rangewidth = highest - lowest; uint16_t scaledbeat = scale16( v, rangewidth ); diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 5b12411e42..1ef3b0fcc0 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -77,7 +77,7 @@ class VirtualStrip { #ifdef MASTER_TUBE // Interface with WLED WS2812FX::load_palette(background.palette_id); - WS2812FX::load_pattern(FX_MODE_EXTERNAL); + WS2812FX::load_pattern(DEFAULT_WLED_FX); stateChanged = true; stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif From efabb94361f2b50d6d9e1f64d5d9c11613cd9ba1 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 00:08:16 -0400 Subject: [PATCH 059/263] Introduce a root relay for commands --- usermods/Tubes/Tubes.h | 17 +++- usermods/Tubes/controller.h | 172 ++++++++++++++++++++++-------------- usermods/Tubes/node.h | 137 ++++++++++++++++------------ usermods/Tubes/options.h | 9 +- 4 files changed, 212 insertions(+), 123 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index edb91ba0bc..2215c134c6 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -73,7 +73,22 @@ class TubesUsermod : public Usermod { uint16_t length = strip.getLengthTotal(); auto external_buffer = strip.get_external_buffer(); - uint8_t fade = sin8(millis() / 40); // amount that Tubes overwrites WLED, 0-255 + uint8_t fade; // amount that Tubes overwrites WLED, 0-255 + switch (this->controller.options.fader) { + case AUTO: + default: + fade = sin8(millis() / 40); + break; + case LEFT: + fade = 255; + break; + case RIGHT: + fade = 0; + break; + case MIDDLE: + fade = 127; + break; + } if (fade > 0) { for (int i = 0, p = 0; i < length; i++, p++) { diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 60eabb4018..967554a95f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -21,8 +21,13 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; typedef struct { bool debugging; uint8_t brightness; + Fader fader; // temp } ControllerOptions; +typedef struct { + TubeState current; + TubeState next; +} TubeStates; #define NUM_VSTRIPS 3 @@ -104,7 +109,6 @@ class PatternController : public MessageReceiver { this->vstrips[i] = new VirtualStrip(num_leds); #endif } - } void setup(bool isMaster) @@ -113,6 +117,7 @@ class PatternController : public MessageReceiver { this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + this->options.fader = AUTO; #ifdef USELCD this->lcd->setup(); @@ -245,7 +250,7 @@ class PatternController : public MessageReceiver { Serial.print(F(" ")); Serial.println(); - this->node->update_status(this->current_state, this->next_state); + this->broadcast_state(); } void background_changed() { @@ -364,20 +369,12 @@ class PatternController : public MessageReceiver { this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; } - void optionsChanged() { -#ifdef NOT_COMPLETE - if (this->isMaster) { - this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); - } -#endif - } - void setBrightness(uint8_t brightness) { Serial.print(F("brightness ")); Serial.println(brightness); this->options.brightness = brightness; - this->optionsChanged(); + this->broadcast_options(); } void setDebugging(bool debugging) { @@ -385,7 +382,7 @@ class PatternController : public MessageReceiver { Serial.println(debugging); this->options.debugging = debugging; - this->optionsChanged(); + this->broadcast_options(); } SyncMode randomSyncMode() { @@ -436,50 +433,6 @@ class PatternController : public MessageReceiver { addFlash(CRGB::Green); } - virtual void onCommand(CommandId command, void *data) { - switch (command) { - case COMMAND_RESET: - Serial.println(F("reset")); - return; - - case COMMAND_BRIGHTNESS: { - uint8_t *bright = (uint8_t *)data; - this->setBrightness(*bright); - Serial.println(); - return; - } - - case COMMAND_OPTIONS: { - Serial.println(F("options")); - memcpy(&this->options, data, sizeof(this->options)); - return; - } - - case COMMAND_UPDATE: { - Serial.print(F(" update ")); - - auto update_data = (NodeUpdate*)data; - - TubeState state; - memcpy(&state, &update_data->current, sizeof(TubeState)); - memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); - state.print(); - this->next_state.print(); - Serial.println(); - - // Catch up to this state - this->load_pattern(state); - this->load_palette(state); - this->load_effect(state); - this->beats->sync(state.bpm, state.beat_frame); - return; - } - } - - Serial.print(F("UNKNOWN ")); - Serial.println(command, HEX); - } - void read_keys() { if (!Serial.available()) return; @@ -526,6 +479,7 @@ class PatternController : public MessageReceiver { } void keyboard_command(char *command) { + // If not the lead, send it to the lead. uint8_t b; accum88 arg = this->parse_number(command+1); @@ -578,33 +532,71 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.pattern_id = arg >> 8; this->next_state.pattern_sync_id = All; - this->update_next(); + this->broadcast_state(); return; - + + case '[': + switch (this->options.fader) { + case LEFT: + this->options.fader = AUTO; + break; + + case RIGHT: + this->options.fader = MIDDLE; + break; + + case MIDDLE: + case AUTO: + default: + this->options.fader = LEFT; + break; + } + this->broadcast_options(); + return; + + case ']': + switch (this->options.fader) { + case RIGHT: + this->options.fader = AUTO; + break; + + case LEFT: + this->options.fader = MIDDLE; + break; + + case MIDDLE: + case AUTO: + default: + this->options.fader = RIGHT; + break; + } + this->broadcast_options(); + return; + case 'm': this->next_state.pattern_phrase = 0; this->next_state.pattern_id = this->current_state.pattern_id; this->next_state.pattern_sync_id = arg >> 8; - this->update_next(); + this->broadcast_state(); return; case 'c': this->next_state.palette_phrase = 0; this->next_state.palette_id = arg >> 8; - this->update_next(); + this->broadcast_state(); return; case 'e': this->next_state.effect_phrase = 0; this->next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; - this->update_next(); + this->broadcast_state(); return; case '%': this->next_state.effect_phrase = 0; this->next_state.effect_params = this->current_state.effect_params; this->next_state.effect_params.chance = arg; - this->update_next(); + this->broadcast_state(); return; case 'g': @@ -626,6 +618,10 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); return; + case 0: + // Empty command + return; + default: Serial.println("dunno?"); return; @@ -638,11 +634,57 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase -= next_phrase; this->next_state.palette_phrase -= next_phrase; this->next_state.effect_phrase -= next_phrase; - this->update_next(); + this->broadcast_state(); + } + + void broadcast_state() { + this->node->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(TubeStates)); } - void update_next() { - this->node->update_status(this->current_state, this->next_state); + void broadcast_options() { + this->node->sendCommand(COMMAND_OPTIONS, &this->options, sizeof(this->options)); + } + + virtual void onCommand(CommandId command, void *data) { + switch (command) { + case COMMAND_RESET: + Serial.println(F("reset")); + return; + + case COMMAND_BRIGHTNESS: { + uint8_t *bright = (uint8_t *)data; + this->setBrightness(*bright); + Serial.println(); + return; + } + + case COMMAND_OPTIONS: + Serial.println(F("options")); + memcpy(&this->options, data, sizeof(this->options)); + return; + + case COMMAND_UPDATE: { + Serial.print(F(" update ")); + + auto update_data = (TubeStates*)data; + + TubeState state; + memcpy(&state, &update_data->current, sizeof(TubeState)); + memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); + state.print(); + this->next_state.print(); + Serial.println(); + + // Catch up to this state + this->load_pattern(state); + this->load_palette(state); + this->load_effect(state); + this->beats->sync(state.bpm, state.beat_frame); + return; + } + } + + Serial.printf("UNKNOWN COMMAND %02X", command); } }; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 57a91568a9..8c929cec5e 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -32,17 +32,18 @@ typedef struct { uint8_t version = CURRENT_NODE_VERSION; } MeshNodeHeader; -typedef struct { - TubeState current; - TubeState next; -} NodeUpdate; +typedef enum{ + ALL=0, + ROOT=1, +} MessageRecipients; + +#define MESSAGE_DATA_SIZE 50 typedef struct { MeshNodeHeader header; + MessageRecipients recipients; CommandId command; - union { - NodeUpdate update; - } data; + byte data[MESSAGE_DATA_SIZE] = {0}; } NodeMessage; void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); @@ -82,19 +83,6 @@ class LightNode { this->receiver = receiver; } - void configure_ap() { - // Try to hide the access point unless this is the "root" node - strcpy(clientSSID, ""); - strcpy(clientPass, ""); - if (this->is_following()) { - sprintf(apSSID, "WLED %03X", this->header.id); - } else { - sprintf(apSSID, "WLED %03X", this->header.id); - } - strcpy(apPass, "WledWled"); - apBehavior = AP_BEHAVIOR_NO_CONN; - } - void onWifiConnect() { if (this->status == NODE_STATUS_QUIET) return; @@ -114,7 +102,7 @@ class LightNode { WiFi.mode (WIFI_MODE_STA); WiFi.disconnect(false, true); quickEspNow.begin(1, WIFI_IF_STA); - this->initialize(); + this->start(); } void onMeshChange() { @@ -126,6 +114,27 @@ class LightNode { this->configure_ap(); } + void configure_ap() { + // Try to hide the access point unless this is the "root" node + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + if (this->is_following()) { + sprintf(apSSID, "WLED %03X F", this->header.id); + } else { + sprintf(apSSID, "WLED %03X", this->header.id); + } + strcpy(apPass, "WledWled"); + apBehavior = AP_BEHAVIOR_NO_CONN; + } + + void start() { + // Initialization timer: wait for a bit before trying to broadcast. + // If this node's ID is high, it's more likely to be the leader, so wait less. + this->status = NODE_STATUS_STARTING; + this->statusTimer.start(3000 - this->header.id / 2); + this->rebroadcastTimer.stop(); + } + void onPeerPing(MeshNodeHeader* node) { // When receiving a message, if the IDs match, it's a conflict // Reset to create a new ID. @@ -179,28 +188,49 @@ class LightNode { // Track that another node exists, updating this node's understanding of the mesh. this->onPeerPing(&message->header); - // Ignore this message if not from my uplink - if (message->header.id != this->header.uplinkId) { + bool ignore = false; + switch (message->recipients) { + case ALL: + // Ignore this message if not from the uplink + ignore = (message->header.id != this->header.uplinkId); + break; + + case ROOT: + // Ignore this message if not from a downlink + ignore = (message->header.uplinkId != this->header.id); + break; + + default: + // ignore this! + ignore = true; + break; + } + + if (ignore) { #ifdef NODE_DEBUGGING Serial.printf(" ignoring\n"); #endif return; - } - + } else { #ifdef NODE_DEBUGGING - Serial.printf(" listening\n"); + Serial.printf(" listening\n"); #endif + } // Execute the received command - Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); - this->receiver->onCommand( - message->command, - &message->data - ); + if (message->recipients != ROOT || !this->is_following()) { + Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); + this->receiver->onCommand( + message->command, + &message->data + ); + } // Re-broadcast the message if appropriate if (!this->rebroadcastTimer.ended()) { message->header = this->header; + if (!this->is_following()) + message->recipients = ALL; this->broadcast(message, true); } } @@ -218,9 +248,26 @@ class LightNode { Serial.printf(">> Broadcast error %d\n", err); } - void setup() { - configure_ap(); + void sendCommand(CommandId command, void *data, uint8_t len) { + if (len > MESSAGE_DATA_SIZE) { + Serial.println("Message is too big!"); + return; + } + NodeMessage message; + message.header = header; + if (this->is_following()) { + // Follower nodes must request that the root re-sends this message + message.recipients = ROOT; + } else { + message.recipients = ALL; + } + message.command = command; + memcpy(&message.data, data, len); + this->broadcast(&message); + } + + void setup() { #ifdef NODE_DEBUGGING this->reset(TESTING_NODE_ID); #else @@ -228,15 +275,8 @@ class LightNode { #endif this->statusTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); - Serial.println("Node: ok"); - } - void initialize() { - // Initialization timer: wait for a bit before trying to broadcast. - // If this node's ID is high, it's more likely to be the leader, so wait less. - this->status = NODE_STATUS_STARTING; - this->statusTimer.start(3000 - this->header.id / 2); - this->rebroadcastTimer.stop(); + Serial.println("Mesh: ok"); } void update() { @@ -259,21 +299,6 @@ class LightNode { } } - void update_status(TubeState ¤t, TubeState &next) { - // Broadcast (or rebroadcast) the current state - NodeMessage message = { - .header = this->header, - .command = COMMAND_UPDATE, - .data = { - .update = { - .current = current, - .next = next - } - } - }; - this->broadcast(&message); - } - void reset(MeshId id = 0) { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 33467b9869..3599d15032 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -1,6 +1,6 @@ #pragma once -typedef enum SyncMode { +typedef enum SyncMode: uint8_t { All=0, SinDrift=1, Pulse=2, @@ -21,6 +21,13 @@ typedef enum Energy: uint8_t { HighEnergy=20, } Energy; +typedef enum Fader: uint8_t { + AUTO = 0, + LEFT = 1, + MIDDLE = 2, + RIGHT = 3 +} Fader; + typedef struct ControlParameters { public: Duration duration=MediumDuration; From 482bb035a7cef95424893f430ad8c0362ee7a4f9 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 01:14:32 -0400 Subject: [PATCH 060/263] Prevent duration divide by zero --- usermods/Tubes/controller.h | 7 +++---- usermods/Tubes/debug.h | 8 ++++---- wled00/FX_fcn.cpp | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 967554a95f..ab8dd6e6c9 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -12,7 +12,7 @@ #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -#define STATUS_UPDATE_PERIOD 2000 +#define STATUS_UPDATE_PERIOD 4000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 @@ -161,15 +161,14 @@ class PatternController : public MessageReceiver { // Update patterns to the beat this->update_beat(); - uint16_t phrase = this->current_state.beat_frame >> 12; // Detect manual overrides & update the current state to match. Segment& segment = WS2812FX::get_strip()->getMainSegment(); if (segment.palette != this->current_state.palette_id) { Serial.printf("Palette override = %d\n",segment.palette); + this->next_state.palette_phrase = 0; this->next_state.palette_id = segment.palette; - this->next_state.palette_phrase = phrase; - this->updateTimer.stop(); + this->broadcast_state(); } // if (segment.mode != FX_MODE_EXTERNAL) { // Serial.printf("Pattern override = %d\n",segment.mode); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 9062358ee6..c82426eb0a 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -79,14 +79,14 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->node->header.id, this->strip->num_leds-1); - this->strip->leds[p2] = CRGB::White; + uint8_t p2 = scale8(this->controller->node->header.id>>4, this->strip->num_leds-1); + this->strip->leds[p2] = CRGB::Yellow; - uint8_t p3 = scale8(this->controller->node->header.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { - this->strip->leds[p3] = CRGB::Yellow; + this->strip->leds[p3] = CRGB::Blue; } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 484e6019c9..a003de6173 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -203,7 +203,6 @@ void Segment::setUpLeds() { else #endif leds = (CRGB*)malloc(sizeof(CRGB)*length()); - } } CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { From e1189398a7c883c661407b6b93b34ede5c4afb6b Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 13:11:00 -0400 Subject: [PATCH 061/263] WLED fixes (and revert changes to .H) --- wled00/FX.h | 1 - wled00/wled.h | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 3333941842..f089dc40b8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -971,7 +971,6 @@ class WS2812FX { // 96 bytes if (seg.mode == pattern_id) continue; seg.startTransition(instance->getTransition()); instance->setMode(i, pattern_id); - seg.markForReset(); } } static WS2812FX* get_strip() { diff --git a/wled00/wled.h b/wled00/wled.h index a14e72c20b..2bb7e9b968 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -12,11 +12,11 @@ //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG -#define WLED_DISABLE_MQTT -#define WLED_DISABLE_LOXONE -#define WLED_DISABLE_ALEXA -#define WLED_DISABLE_INFRARED -#define WLED_DISABLE_CRONIXIE +//#define WLED_DISABLE_MQTT +//#define WLED_DISABLE_LOXONE +//#define WLED_DISABLE_ALEXA +//#define WLED_DISABLE_INFRARED +//#define WLED_DISABLE_CRONIXIE // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From bf4f8e441eb14624ecc27892a48650515fdccdce Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 13:11:43 -0400 Subject: [PATCH 062/263] web-based OTA updates + allow wifi connections --- usermods/Tubes/controller.h | 5 ++ usermods/Tubes/node.h | 12 ++--- usermods/Tubes/updater.h | 104 ++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 usermods/Tubes/updater.h diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ab8dd6e6c9..5c345d177a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -2,6 +2,7 @@ #include "wled.h" #include "FX.h" +#include "updater.h" #include "beats.h" @@ -617,6 +618,10 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); return; + case 'U': + WifiUpdater().web_update(); + return; + case 0: // Empty command return; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 8c929cec5e..e8e9b6c12c 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -116,8 +116,6 @@ class LightNode { void configure_ap() { // Try to hide the access point unless this is the "root" node - strcpy(clientSSID, ""); - strcpy(clientPass, ""); if (this->is_following()) { sprintf(apSSID, "WLED %03X F", this->header.id); } else { @@ -291,11 +289,11 @@ class LightNode { if (this->status == NODE_STATUS_STARTING) this->status = NODE_STATUS_STARTED; - // Check WiFi status and update node status if wifi changed - if (WiFi.isConnected()) - this->onWifiConnect(); - else - this->onWifiDisconnect(); + // // Check WiFi status and update node status if wifi changed + // if (WiFi.isConnected()) + // this->onWifiConnect(); + // else + // this->onWifiDisconnect(); } } diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h new file mode 100644 index 0000000000..d35ea1dc40 --- /dev/null +++ b/usermods/Tubes/updater.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include + +// Utility to extract header value from headers +String getHeaderValue(String header, String headerName) { + return header.substring(strlen(headerName.c_str())); +} + +class WifiUpdater { + public: + String host = "kwater.kelectronics.net"; + String bin = "/api/getfirmware/firmwareLarge.bin"; + int port = 80; + + void web_update() { + WiFiClient client; + long fileSize = 0; + String contentType = ""; + + if (!WiFi.isConnected()) { + Serial.println("Not on WiFi"); + return; + } + + Serial.println("Connecting"); + if (!client.connect(host.c_str(), port)) { + Serial.println("Connect failed"); + client.stop(); + return; + } + // Get the contents of the bin file + client.print(String("GET ") + bin + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "Cache-Control: no-cache\r\n" + + "Connection: close\r\n\r\n"); + + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 5000) { + Serial.println("Client Timeout !"); + client.stop(); + return; + } + } + + Serial.println("Reading headers"); + while (client.available()) { + // read line till /n - if the line is empty, it's the end of the headers. + String line = client.readStringUntil('\n'); + line.trim(); + if (!line.length()) break; + + // Check if the HTTP Response is 200 + if (line.startsWith("HTTP/1.1")) { + if (line.indexOf("200") < 0) { + Serial.println("Got a non 200 status code from server. Exiting OTA Update."); + client.flush(); + return; + } + } + + // Read the file size from Content-Length + if (line.startsWith("Content-Length: ")) { + fileSize = atol((getHeaderValue(line, "Content-Length: ")).c_str()); + Serial.println(line); + } + + // Read the content type from Content-Type + if (line.startsWith("Content-Type: ")) { + String contentType = getHeaderValue(line, "Content-Type: "); + Serial.println(line); + } + } + + if (fileSize == 0 || contentType != "application/octet-stream") { + Serial.println("Must get a valid Content-Type and Content-Length header."); + client.flush(); + return; + } + + Serial.println("Beginning update"); + if (!Update.begin(fileSize)) { + Serial.println("Cannot do the update"); + return; + }; + Update.writeStream(client); + if (!Update.end()) { + Serial.println("Error Occurred. Error #: " + String(Update.getError())); + } + + if (Update.isFinished()) { + Serial.println("Update successfully completed. Rebooting."); + ESP.restart(); + } else { + Serial.println("Update not finished? Something went wrong!"); + } + + client.flush(); + return; + } +}; \ No newline at end of file From d26bce4cb4a561f038e3c4ad5289c03881a5b947 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 17:01:15 -0400 Subject: [PATCH 063/263] Fix small issues in updater --- usermods/Tubes/updater.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index d35ea1dc40..4a2a3927a2 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -54,9 +54,9 @@ class WifiUpdater { if (!line.length()) break; // Check if the HTTP Response is 200 - if (line.startsWith("HTTP/1.1")) { + if (line.startsWith("HTTP/")) { if (line.indexOf("200") < 0) { - Serial.println("Got a non 200 status code from server. Exiting OTA Update."); + Serial.println("Got a non 200 status code"); client.flush(); return; } @@ -70,7 +70,7 @@ class WifiUpdater { // Read the content type from Content-Type if (line.startsWith("Content-Type: ")) { - String contentType = getHeaderValue(line, "Content-Type: "); + contentType = getHeaderValue(line, "Content-Type: "); Serial.println(line); } } From 2f4b0c48974934391a5f09d5dc5d900d6c408e78 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 01:38:25 -0400 Subject: [PATCH 064/263] better debug info; more frequent updates; remove "external buffer" because it's no longer necessary; revert files to upstream --- usermods/Tubes/Tubes.h | 7 +------ usermods/Tubes/controller.h | 4 +--- usermods/Tubes/debug.h | 30 ++++++++++++++++++++++++------ usermods/Tubes/led_strip.h | 11 +++++++---- usermods/Tubes/node.h | 10 +++++----- usermods/Tubes/util.h | 2 +- wled00/FX.cpp | 19 +------------------ wled00/FX.h | 5 ----- wled00/wled.cpp | 2 -- wled00/wled.h | 7 +------ wled00/wled_serial.cpp | 1 - 11 files changed, 41 insertions(+), 57 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 2215c134c6..d9d16a48ad 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -71,7 +71,6 @@ class TubesUsermod : public Usermod { // uint8_t segment_id = strip.getMainSegmentId(); uint16_t length = strip.getLengthTotal(); - auto external_buffer = strip.get_external_buffer(); uint8_t fade; // amount that Tubes overwrites WLED, 0-255 switch (this->controller.options.fader) { @@ -92,12 +91,8 @@ class TubesUsermod : public Usermod { if (fade > 0) { for (int i = 0, p = 0; i < length; i++, p++) { - if (p >= EXTERNAL_BUFFER_SIZE) { - p = 0; - } - CRGB color1 = strip.getPixelColor(i); - CRGB color2 = external_buffer[p]; + CRGB color2 = controller.led_strip->getPixelColor(i); uint8_t r = blend8(color1.r, color2.r, fade); uint8_t g = blend8(color1.g, color2.g, fade); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 5c345d177a..3a43023b31 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -13,7 +13,7 @@ #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -#define STATUS_UPDATE_PERIOD 4000 +#define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 @@ -132,8 +132,6 @@ class PatternController : public MessageReceiver { this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); - - WS2812FX::load_pattern(FX_MODE_EXTERNAL); } void do_pattern_changes() { diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c82426eb0a..eba7ddaa39 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -57,20 +57,38 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n\n", + // Dump internal status + auto knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + auto knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + Serial.printf("\n=== %s%s WiFi[ch%d] %s IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n", this->controller->node->node_name, status_code(this->controller->node->status).c_str(), - WiFi.status(), WiFi.channel(), - WiFi.localIP()[0], - WiFi.localIP()[1], - WiFi.localIP()[2], - WiFi.localIP()[3], + knownSsid.c_str(), + knownIp[0], + knownIp[1], + knownIp[2], + knownIp[3], freeMemory(), LITTLEFS.usedBytes(), LITTLEFS.totalBytes(), formatted_time(millis()).c_str() ); + + // Dump WLED status + char mode_name[50]; + char palette_name[50]; + auto seg = WS2812FX::getInstance()->getMainSegment(); + extractModeName(seg.mode, JSON_mode_names, mode_name, 50); + extractModeName(seg.palette, JSON_palette_names, palette_name, 50); + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u\n\n", + mode_name, + seg.mode, + palette_name, + seg.palette, + seg.speed, + seg.intensity + ); } // Show the beat on the master OR if debugging diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 2ab09732f3..e46c067714 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -34,10 +34,7 @@ class LEDs { } void show() { - CRGB *external_buffer = WS2812FX::get_external_buffer(); - for (int i = 0; i < num_leds; i++) { - external_buffer[i] = leds[i]; - } + // There's nothing to do right now, because in Tubes.h we blend the LEDs into } void update(bool reverse=false) { @@ -57,4 +54,10 @@ class LEDs { this->fps = 0; } } + + CRGB getPixelColor(uint8_t pos) { + if (pos > this->num_leds) + return CRGB::Black; + return this->leds[pos]; + } }; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index e8e9b6c12c..89304f083b 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -289,11 +289,11 @@ class LightNode { if (this->status == NODE_STATUS_STARTING) this->status = NODE_STATUS_STARTED; - // // Check WiFi status and update node status if wifi changed - // if (WiFi.isConnected()) - // this->onWifiConnect(); - // else - // this->onWifiDisconnect(); + // Check WiFi status and update node status if wifi changed + if (WiFi.isConnected()) + this->onWifiConnect(); + else + this->onWifiDisconnect(); } } diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index e49f208697..2b353c7e1e 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -2,7 +2,7 @@ #include "wled.h" -// Is this the tube that can control others? +// Is this a tube that can control WLED? #define MASTER_TUBE #define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 465c873585..ad3fac55ee 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5279,22 +5279,6 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;!,!,!;!;2d"; -uint16_t mode_external(void) { - // uint8_t segment_id = strip.getMainSegmentId(); - uint16_t length = strip.getLengthTotal(); - auto external_buffer = strip.get_external_buffer(); - - for (int i = 0, p = 0; i < length; i++, p++) { - if (p >= EXTERNAL_BUFFER_SIZE) { - p = 0; - } - strip.setPixelColor(i, external_buffer[p]); - } - - return FRAMETIME; -} -static const char _data_FX_MODE_EXTERNAL[] PROGMEM = "External!@!,;!,!,;!;1d"; - //////////////////////////////// // 2D Polar Lights // //////////////////////////////// @@ -7463,8 +7447,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE); addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); - // addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); - addEffect(FX_MODE_EXTERNAL, &mode_external, _data_FX_MODE_EXTERNAL); + addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); // --- 2D non-audio effects --- diff --git a/wled00/FX.h b/wled00/FX.h index f089dc40b8..38976dda7b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -944,12 +944,7 @@ class WS2812FX { // 96 bytes void estimateCurrentAndLimitBri(void); - CRGB external_buffer[EXTERNAL_BUFFER_SIZE]; // 4 bytes per element - public: - static CRGB* get_external_buffer() { - return instance->external_buffer; - } static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { Segment& segment = instance->getMainSegment(); uint32_t color = segment.color_from_palette(c, false, true, 255, brightness); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index e767b3fe5c..c9f590fb33 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -449,8 +449,6 @@ void WLED::initAP(bool resetAP) strcpy_P(apPass, PSTR(DEFAULT_AP_PASS)); DEBUG_PRINT(F("Opening access point ")); DEBUG_PRINTLN(apSSID); - DEBUG_PRINT(F("Password ")); - DEBUG_PRINTLN(apPass); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); diff --git a/wled00/wled.h b/wled00/wled.h index 2bb7e9b968..055d65d3ed 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -12,11 +12,6 @@ //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG -//#define WLED_DISABLE_MQTT -//#define WLED_DISABLE_LOXONE -//#define WLED_DISABLE_ALEXA -//#define WLED_DISABLE_INFRARED -//#define WLED_DISABLE_CRONIXIE // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. @@ -49,7 +44,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -// #define WLED_DEBUG +//#define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index 2defd62cec..a73127e040 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -36,7 +36,6 @@ void updateBaudRate(uint32_t rate){ void handleSerial() { - return; if (pinManager.isPinAllocated(3)) return; #ifdef WLED_ENABLE_ADALIGHT From f15dc0a846219f180964ce55c7fa1173fe65b42d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 01:57:18 -0400 Subject: [PATCH 065/263] Completely back out modifications to core WLED --- usermods/Tubes/Tubes.h | 2 +- usermods/Tubes/controller.h | 2 +- usermods/Tubes/debug.h | 19 +++++++------ usermods/Tubes/particle.h | 4 +-- usermods/Tubes/virtual_strip.h | 52 +++++++++++++++++++++++++--------- wled00/FX.h | 30 +------------------- 6 files changed, 53 insertions(+), 56 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index d9d16a48ad..a7b47939c2 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -42,7 +42,7 @@ class TubesUsermod : public Usermod { bootPreset = 0; // Try to prevent initial playlists from starting fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time - WS2812FX::load_pattern(DEFAULT_WLED_FX); // Crossfade with FLOW + VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX); // Start timing globalTimer.setup(); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3a43023b31..99db4f7092 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -162,7 +162,7 @@ class PatternController : public MessageReceiver { this->update_beat(); // Detect manual overrides & update the current state to match. - Segment& segment = WS2812FX::get_strip()->getMainSegment(); + Segment& segment = strip.getMainSegment(); if (segment.palette != this->current_state.palette_id) { Serial.printf("Palette override = %d\n",segment.palette); this->next_state.palette_phrase = 0; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index eba7ddaa39..404b7f5389 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -2,6 +2,7 @@ #include "controller.h" #include "node.h" +#include "wled.h" std::string formatted_time(long ms) { long secs = ms / 1000; // set the seconds remaining @@ -24,14 +25,14 @@ std::string formatted_time(long ms) { class DebugController { public: PatternController *controller; - LEDs *strip; + LEDs *led_strip; LightNode *node; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; - this->strip = controller->led_strip; + this->led_strip = controller->led_strip; this->node = controller->node; } @@ -78,7 +79,7 @@ class DebugController { // Dump WLED status char mode_name[50]; char palette_name[50]; - auto seg = WS2812FX::getInstance()->getMainSegment(); + auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u\n\n", @@ -95,16 +96,16 @@ class DebugController { if (this->controller->options.debugging) { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; - this->strip->leds[p1] = CRGB::White; + this->led_strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->node->header.id>>4, this->strip->num_leds-1); - this->strip->leds[p2] = CRGB::Yellow; + uint8_t p2 = scale8(this->controller->node->header.id>>4, this->led_strip->num_leds-1); + this->led_strip->leds[p2] = CRGB::Yellow; - uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->led_strip->num_leds-1); if (p3 == p2) { - this->strip->leds[p3] = CRGB::Green; + this->led_strip->leds[p3] = CRGB::Green; } else { - this->strip->leds[p3] = CRGB::Blue; + this->led_strip->leds[p3] = CRGB::Blue; } } diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d1afb6a69d..fb0ba887ee 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -97,7 +97,7 @@ class Particle { } void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { - CRGB c = CRGB(WS2812FX::get_strip()->getPixelColor(pos)); + CRGB c = CRGB(strip.getPixelColor(pos)); CRGB new_color; switch (this->pen) { @@ -152,7 +152,7 @@ class Particle { return; } - WS2812FX::get_strip()->setPixelColor(pos, new_color); + strip.setPixelColor(pos, new_color); } }; diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 1ef3b0fcc0..f3ec25cc86 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -4,6 +4,7 @@ #include "options.h" #include "beats.h" #include "palettes.h" +#include "wled.h" #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 @@ -42,6 +43,8 @@ class VirtualStrip { const static uint16_t DEF_BRIGHT = 255; public: + bool isWled = true; + CRGB leds[MAX_VIRTUAL_LEDS]; uint8_t num_leds; uint8_t brightness; @@ -74,15 +77,35 @@ class VirtualStrip { this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; -#ifdef MASTER_TUBE - // Interface with WLED - WS2812FX::load_palette(background.palette_id); - WS2812FX::load_pattern(DEFAULT_WLED_FX); - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); -#endif + if (this->isWled) { + set_wled_palette(background.palette_id); + set_wled_pattern(DEFAULT_WLED_FX); + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } + } + + static void set_wled_palette(uint8_t palette_id) { + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (seg.palette == palette_id) continue; + if (!seg.isActive()) continue; + seg.startTransition(strip.getTransition()); + seg.palette = palette_id; + } + } + + static void set_wled_pattern(uint8_t pattern_id) { + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (!seg.isActive()) continue; + if (seg.mode == pattern_id) continue; + seg.startTransition(strip.getTransition()); + strip.setMode(i, pattern_id); + } } + void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) { if (this->fade == Dead) @@ -166,13 +189,14 @@ class VirtualStrip { } CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { -#define WLED_COLORS -#ifdef WLED_COLORS - return WS2812FX::get_palette_crgb(c + offset, brightness); -#else - CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; - return ColorFromPalette( palette, c + offset ); -#endif + if (this->isWled) { + Segment& segment = strip.getMainSegment(); + uint32_t color = segment.color_from_palette(c + offset, false, true, 255, brightness); + return CRGB(color); + } else { + CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; + return ColorFromPalette( palette, c + offset ); + } } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { diff --git a/wled00/FX.h b/wled00/FX.h index 38976dda7b..a9187ae0f8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -334,6 +334,7 @@ #endif #ifndef USERMOD_AUDIOREACTIVE + #ifndef WLED_DISABLE_AUDIO #define MODE_COUNT 163 #else @@ -943,35 +944,6 @@ class WS2812FX { // 96 bytes void estimateCurrentAndLimitBri(void); - - public: - static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { - Segment& segment = instance->getMainSegment(); - uint32_t color = segment.color_from_palette(c, false, true, 255, brightness); - return CRGB(color); - } - static void load_palette(uint8_t palette_id) { - for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { - Segment& seg = instance->getSegment(i); - if (seg.palette == palette_id) continue; - if (!seg.isActive()) continue; - seg.startTransition(instance->getTransition()); - seg.palette = palette_id; - } - } - static void load_pattern(uint8_t pattern_id) { - for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { - Segment& seg = instance->getSegment(i); - if (!seg.isActive()) continue; - if (seg.mode == pattern_id) continue; - seg.startTransition(instance->getTransition()); - instance->setMode(i, pattern_id); - } - } - static WS2812FX* get_strip() { - return instance; - } - }; extern const char JSON_mode_names[]; From e1fb6cbf051ea3d0977811f54910ef4e1dce5940 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 02:39:54 -0400 Subject: [PATCH 066/263] Better node debugging --- usermods/Tubes/node.h | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 89304f083b..78cee2d783 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -15,6 +15,7 @@ #include "global_state.h" // #define NODE_DEBUGGING +// #define RELAY_DEBUGGING #define TESTING_NODE_ID 100 #define CURRENT_NODE_VERSION 1 @@ -48,6 +49,18 @@ typedef struct { void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); +char *command_name(CommandId command) { + switch (command) { + case COMMAND_UPDATE: + return "UPDATE"; + case COMMAND_OPTIONS: + return "OPTIONS"; + case COMMAND_RESET: + return "RESET"; + default: + return "?COMMAND?"; + } +} class MessageReceiver { public: @@ -143,8 +156,11 @@ class LightNode { // If the message arrives from a higher ID, switch into follower mode if (node->id > this->header.uplinkId && node->id > this->header.id) { - if (this->header.id != TESTING_NODE_ID || node->id < 0x800) - this->follow(node); +#ifdef RELAY_DEBUGGING + // When debugging relay, pretend not to see any nodes above 0x800 + if (node->id < 0x800) +#endif + this->follow(node); } // If the message arrived from our uplink, track that we're still linked. @@ -170,7 +186,10 @@ class LightNode { NodeMessage* message = (NodeMessage*)data; #ifdef NODE_DEBUGGING Serial.printf(">> Received %db ", len); - Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + Serial.print(command_name(message->command)); + if (message->recipients == ROOT) + Serial.printf(":ROOT"); + Serial.printf(" from %03X/%03X ", message->header.id, message->header.uplinkId); Serial.printf("at " MACSTR, MAC2STR(address)); Serial.printf("@ %ddBm: ", rssi); #endif @@ -211,7 +230,7 @@ class LightNode { return; } else { #ifdef NODE_DEBUGGING - Serial.printf(" listening\n"); + Serial.printf("\n"); #endif } @@ -238,6 +257,14 @@ class LightNode { if (this->status != NODE_STATUS_STARTED) return; +#ifdef NODE_DEBUGGING + Serial.printf(">> Sending %db ", sizeof(*message)); + Serial.print(command_name(message->command)); + if (message->recipients == ROOT) + Serial.printf(":ROOT"); + Serial.println(); +#endif + auto err = quickEspNow.send( ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message, sizeof(*message) @@ -254,7 +281,7 @@ class LightNode { NodeMessage message; message.header = header; - if (this->is_following()) { + if (command != COMMAND_UPDATE && this->is_following()) { // Follower nodes must request that the root re-sends this message message.recipients = ROOT; } else { From a3c54eee494a1cad3b3476c1d961d24adf19352d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 03:01:31 -0400 Subject: [PATCH 067/263] OPTIONS command --- usermods/Tubes/controller.h | 18 ++++++------------ usermods/Tubes/global_state.h | 4 +--- usermods/Tubes/virtual_strip.h | 4 ++++ wled00/wled_serial.cpp | 2 ++ 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 99db4f7092..76f86bab3f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -257,6 +257,10 @@ class PatternController : public MessageReceiver { Serial.println(); } + void load_options(ControllerOptions &options) { + strip.setBrightness(options.brightness); + } + void load_pattern(TubeState &tube_state) { if (this->current_state.pattern_id == tube_state.pattern_id && this->current_state.pattern_sync_id == tube_state.pattern_sync_id) @@ -650,24 +654,15 @@ class PatternController : public MessageReceiver { virtual void onCommand(CommandId command, void *data) { switch (command) { case COMMAND_RESET: - Serial.println(F("reset")); - return; - - case COMMAND_BRIGHTNESS: { - uint8_t *bright = (uint8_t *)data; - this->setBrightness(*bright); - Serial.println(); + // TODO return; - } case COMMAND_OPTIONS: - Serial.println(F("options")); memcpy(&this->options, data, sizeof(this->options)); + this->load_options(this->options); return; case COMMAND_UPDATE: { - Serial.print(F(" update ")); - auto update_data = (TubeStates*)data; TubeState state; @@ -675,7 +670,6 @@ class PatternController : public MessageReceiver { memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); state.print(); this->next_state.print(); - Serial.println(); // Catch up to this state this->load_pattern(state); diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index bf4ba4f2e5..a19646ec09 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,6 +57,4 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_RESET = 0xF0; - -const static CommandId COMMAND_BRIGHTNESS = 0x80; +const static CommandId COMMAND_RESET = 0xF0; \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index f3ec25cc86..91d33347b5 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -105,6 +105,10 @@ class VirtualStrip { } } + static void set_wled_brightness(uint8_t brightness) { + strip.setBrightness(brightness); + } + void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) { diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index a73127e040..7f3213891a 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -36,6 +36,8 @@ void updateBaudRate(uint32_t rate){ void handleSerial() { + return; + if (pinManager.isPinAllocated(3)) return; #ifdef WLED_ENABLE_ADALIGHT From 101a85cd8e6627751261fa02a920015947c953e9 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 12:45:12 -0400 Subject: [PATCH 068/263] Better debugging output --- usermods/Tubes/Tubes.h | 1 - usermods/Tubes/node.h | 53 ++++++++++++++++++---------------- usermods/Tubes/util.h | 7 ----- usermods/Tubes/virtual_strip.h | 1 + 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index a7b47939c2..1c9808d6b9 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -42,7 +42,6 @@ class TubesUsermod : public Usermod { bootPreset = 0; // Try to prevent initial playlists from starting fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time - VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX); // Start timing globalTimer.setup(); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 78cee2d783..685fd4cc79 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -21,8 +21,8 @@ #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost -#define REBROADCAST_TIME 15000 // Time at which followers are presumed re-uplinked +#define UPLINK_TIMEOUT 20000 // Time at which uplink is presumed lost +#define REBROADCAST_TIME 30000 // Time at which followers are presumed re-uplinked #define WIFI_CHECK_RATE 2000 // Time at which we should check wifi status again typedef uint16_t MeshId; @@ -178,26 +178,30 @@ class LightNode { } } + void print_message(NodeMessage* message, signed int rssi) { + Serial.printf("%03X/%03X %s", + message->header.id, + message->header.uplinkId, + command_name(message->command) + ); + if (message->recipients == ROOT) + Serial.printf(":ROOT"); + if (rssi) + Serial.printf(" %ddB ", rssi); + } + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { // Ignore this message if it isn't a valid message payload. if (len != sizeof(NodeMessage)) return; NodeMessage* message = (NodeMessage*)data; -#ifdef NODE_DEBUGGING - Serial.printf(">> Received %db ", len); - Serial.print(command_name(message->command)); - if (message->recipients == ROOT) - Serial.printf(":ROOT"); - Serial.printf(" from %03X/%03X ", message->header.id, message->header.uplinkId); - Serial.printf("at " MACSTR, MAC2STR(address)); - Serial.printf("@ %ddBm: ", rssi); -#endif - // Ignore this message if it's the wrong version. if (message->header.version != this->header.version) { #ifdef NODE_DEBUGGING - Serial.printf(" recipients != ROOT || !this->is_following()) { - Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); + Serial.print(" >> "); + print_message(message, rssi); + Serial.print(" "); this->receiver->onCommand( message->command, &message->data ); + Serial.println(); } // Re-broadcast the message if appropriate @@ -258,10 +263,8 @@ class LightNode { return; #ifdef NODE_DEBUGGING - Serial.printf(">> Sending %db ", sizeof(*message)); - Serial.print(command_name(message->command)); - if (message->recipients == ROOT) - Serial.printf(":ROOT"); + Serial.print(" <<< "); + print_message(message, 0); Serial.println(); #endif @@ -270,7 +273,7 @@ class LightNode { (uint8_t*)message, sizeof(*message) ); if (err) - Serial.printf(">> Broadcast error %d\n", err); + Serial.printf(" *** Broadcast error %d\n", err); } void sendCommand(CommandId command, void *data, uint8_t len) { diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index 2b353c7e1e..f0fc596f98 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -1,12 +1,5 @@ #pragma once -#include "wled.h" - -// Is this a tube that can control WLED? -#define MASTER_TUBE - -#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS - uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { uint16_t rangewidth = highest - lowest; uint16_t scaledbeat = scale16( v, rangewidth ); diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 91d33347b5..44bd18553d 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -9,6 +9,7 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 +#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS // #define TESTING_PATTERNS class VirtualStrip; From 046c1a71bc20100b5d6975af5de1f8eb8ad1187e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 14 Aug 2022 03:27:44 -0700 Subject: [PATCH 069/263] Power save mode; Ability to set WLED patterns; better debug overlays; Increase number of LEDs; --- usermods/Tubes/Tubes.h | 14 +++-- usermods/Tubes/controller.h | 95 ++++++++++++++++++++++++++++++---- usermods/Tubes/debug.h | 18 ++++--- usermods/Tubes/effects.h | 2 - usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/node.h | 18 ++++++- usermods/Tubes/options.h | 8 +-- usermods/Tubes/pattern.h | 4 -- usermods/Tubes/virtual_strip.h | 15 +++--- 9 files changed, 131 insertions(+), 45 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 1c9808d6b9..89429aed5d 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -42,6 +42,7 @@ class TubesUsermod : public Usermod { bootPreset = 0; // Try to prevent initial playlists from starting fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time + strip.setTargetFps(60); // Start timing globalTimer.setup(); @@ -67,10 +68,6 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { // Perform a cross-fade between current WLED mode and the external buffer - - // uint8_t segment_id = strip.getMainSegmentId(); - uint16_t length = strip.getLengthTotal(); - uint8_t fade; // amount that Tubes overwrites WLED, 0-255 switch (this->controller.options.fader) { case AUTO: @@ -87,9 +84,10 @@ class TubesUsermod : public Usermod { fade = 127; break; } - + if (fade > 0) { - for (int i = 0, p = 0; i < length; i++, p++) { + uint16_t length = strip.getLengthTotal(); + for (int i = 0; i < length; i++) { CRGB color1 = strip.getPixelColor(i); CRGB color2 = controller.led_strip->getPixelColor(i); @@ -102,8 +100,8 @@ class TubesUsermod : public Usermod { } // Draw effects layers over whatever WLED is doing. - WS2812FX* leds = &strip; - controller.effects->draw(leds); + this->controller.overlay(); + this->debug.overlay(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 76f86bab3f..57ab0f2472 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -21,8 +21,15 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; typedef struct { bool debugging; + bool power_save; uint8_t brightness; Fader fader; // temp + + uint8_t wled_pattern; + uint8_t speed; + uint8_t intensity; + + uint8_t reserved[12]; } ControllerOptions; typedef struct { @@ -66,7 +73,7 @@ class Button { class PatternController : public MessageReceiver { public: - const static int FRAMES_PER_SECOND = 300; // how often we animate, in frames per second + const static int FRAMES_PER_SECOND = 100; // how often we animate, in frames per second const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds uint8_t num_leds; @@ -116,9 +123,18 @@ class PatternController : public MessageReceiver { { this->node->setup(); this->isMaster = isMaster; + this->options.power_save = false; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; +#ifdef TESTING_PATTERNS + this->options.fader = RIGHT; +#else this->options.fader = AUTO; +#endif + this->options.wled_pattern = DEFAULT_WLED_FX; + this->options.speed = strip.getMainSegment().speed; + this->options.intensity = strip.getMainSegment().intensity; + this->load_options(this->options); #ifdef USELCD this->lcd->setup(); @@ -169,9 +185,25 @@ class PatternController : public MessageReceiver { this->next_state.palette_id = segment.palette; this->broadcast_state(); } - // if (segment.mode != FX_MODE_EXTERNAL) { - // Serial.printf("Pattern override = %d\n",segment.mode); - // } + + bool options_changed = false; + if (segment.mode != this->options.wled_pattern) { + Serial.printf("WLED FX mode: %d\n",segment.mode); + this->options.wled_pattern = segment.mode; + options_changed = true; + } + if (segment.speed != this->options.speed) { + Serial.printf("WLED FX speed: %d\n",segment.speed); + this->options.speed = segment.speed; + options_changed = true; + } + if (segment.intensity != this->options.intensity) { + Serial.printf("WLED FX intensity: %d\n",segment.intensity); + this->options.intensity = segment.intensity; + options_changed = true; + } + if (options_changed) + this->broadcast_options(); do_pattern_changes(); @@ -201,6 +233,22 @@ class PatternController : public MessageReceiver { #endif } + void overlay() { + if (this->options.power_save) { + // Screen door effect + uint16_t length = strip.getLengthTotal(); + for (int i = 0; i < length; i++) { + if (i % 2) { + CRGB c = strip.getPixelColor(i); + strip.setPixelColor(i, CRGB(c.r>>3,c.g>>3,c.b>>3)); + } + } + } + + // Draw effects layers over whatever WLED is doing. + this->effects->draw(&strip); + } + void restart_phrase() { this->beats->start_phrase(); this->update_beat(); @@ -259,6 +307,7 @@ class PatternController : public MessageReceiver { void load_options(ControllerOptions &options) { strip.setBrightness(options.brightness); + VirtualStrip::set_wled_pattern(options.wled_pattern, options.speed, options.intensity); } void load_pattern(TubeState &tube_state) { @@ -372,25 +421,27 @@ class PatternController : public MessageReceiver { } void setBrightness(uint8_t brightness) { - Serial.print(F("brightness ")); - Serial.println(brightness); + Serial.printf("brightness: %d\n", brightness); this->options.brightness = brightness; this->broadcast_options(); } void setDebugging(bool debugging) { - Serial.print(F("debugging ")); - Serial.println(debugging); + Serial.printf("debugging: %d\n", debugging); this->options.debugging = debugging; this->broadcast_options(); } + void setPowerSave(bool power_save) { + Serial.printf("power_save: %d\n", power_save); + + this->options.power_save = power_save; + this->broadcast_options(); + } + SyncMode randomSyncMode() { - #ifdef TESTING_PATTERNS - return All; - #endif uint8_t r = random8(128); if (r < 40) return SinDrift; @@ -489,6 +540,9 @@ class PatternController : public MessageReceiver { case 'd': this->setDebugging(!this->options.debugging); break; + case '@': + this->setPowerSave(!this->options.power_save); + break; case '-': b = this->options.brightness; @@ -601,6 +655,15 @@ class PatternController : public MessageReceiver { this->broadcast_state(); return; + case 'w': + Serial.printf("Setting WLED FX to %d:128:128\n", arg >> 8); + this->options.wled_pattern = arg >> 8; + this->options.speed = 128; + this->options.intensity = 128; + this->load_options(this->options); + this->broadcast_options(); + return; + case 'g': for (int i=0; i< 10; i++) addGlitter(); @@ -614,10 +677,12 @@ class PatternController : public MessageReceiver { Serial.println(F("m### - sync mode")); Serial.println(F("c### - colors")); Serial.println(F("e### - effects")); + Serial.println("w### - wled pattern"); Serial.println(); Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); + Serial.println("@ - power save mode"); return; case 'U': @@ -660,6 +725,14 @@ class PatternController : public MessageReceiver { case COMMAND_OPTIONS: memcpy(&this->options, data, sizeof(this->options)); this->load_options(this->options); + Serial.printf("[debug=%d bri=%d fader=%d wled:%d:%d:%d]", + this->options.debugging, + this->options.brightness, + this->options.fader, + this->options.wled_pattern, + this->options.speed, + this->options.intensity + ); return; case COMMAND_UPDATE: { diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 404b7f5389..c5010fb8f4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -91,21 +91,25 @@ class DebugController { seg.intensity ); } + } + void overlay() + { // Show the beat on the master OR if debugging - if (this->controller->options.debugging) { + uint16_t num_leds = strip.getLengthTotal(); + uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; - this->led_strip->leds[p1] = CRGB::White; + strip.setPixelColor(p1, CRGB::White); - uint8_t p2 = scale8(this->controller->node->header.id>>4, this->led_strip->num_leds-1); - this->led_strip->leds[p2] = CRGB::Yellow; + uint8_t p2 = scale8(this->controller->node->header.id>>4, num_leds-1); + strip.setPixelColor(p2, CRGB::Yellow); - uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->led_strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, num_leds-1); if (p3 == p2) { - this->led_strip->leds[p3] = CRGB::Green; + strip.setPixelColor(p3, CRGB::Green); } else { - this->led_strip->leds[p3] = CRGB::Blue; + strip.setPixelColor(p3, CRGB::Blue); } } diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 47308197cd..eabb1c5e3f 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -131,7 +131,6 @@ typedef struct { static const EffectDef gEffects[] = { {{None}, {LongDuration}}, -#ifndef TESTING_PATTERNS {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, @@ -151,6 +150,5 @@ static const EffectDef gEffects[] = { {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, -#endif }; const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index e46c067714..a4aca410c5 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -3,7 +3,7 @@ #define USE_WLED #include "wled.h" -#define MAX_REAL_LEDS 100 +#define MAX_REAL_LEDS 150 class LEDs { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 685fd4cc79..bf1465e8e4 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -16,9 +16,9 @@ // #define NODE_DEBUGGING // #define RELAY_DEBUGGING -#define TESTING_NODE_ID 100 +#define TESTING_NODE_ID 0 -#define CURRENT_NODE_VERSION 1 +#define CURRENT_NODE_VERSION 2 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS #define UPLINK_TIMEOUT 20000 // Time at which uplink is presumed lost @@ -43,6 +43,7 @@ typedef enum{ typedef struct { MeshNodeHeader header; MessageRecipients recipients; + uint32_t timebase; CommandId command; byte data[MESSAGE_DATA_SIZE] = {0}; } NodeMessage; @@ -128,6 +129,9 @@ class LightNode { } void configure_ap() { + // Don't connect to any networks. + strcpy(clientSSID, ""); + // Try to hide the access point unless this is the "root" node if (this->is_following()) { sprintf(apSSID, "WLED %03X F", this->header.id); @@ -241,6 +245,15 @@ class LightNode { Serial.print(" >> "); print_message(message, rssi); Serial.print(" "); + + // Adjust the timebase to match uplink + // But only if it's drifting, else animations get jittery + uint32_t new_timebase = message->timebase - millis() + 3; // Factor for network delay + int32_t diff = new_timebase - strip.timebase; + if (diff < -10 || diff > 10) + strip.timebase = new_timebase; + + // Execute the command this->receiver->onCommand( message->command, &message->data @@ -261,6 +274,7 @@ class LightNode { // Don't broadcast anything if this node isn't active. if (this->status != NODE_STATUS_STARTED) return; + message->timebase = strip.timebase + millis(); #ifdef NODE_DEBUGGING Serial.print(" <<< "); diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 3599d15032..e6c0f0eac6 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -22,10 +22,10 @@ typedef enum Energy: uint8_t { } Energy; typedef enum Fader: uint8_t { - AUTO = 0, - LEFT = 1, - MIDDLE = 2, - RIGHT = 3 + AUTO = 0, // Does a sin pattern + LEFT = 1, // All left (internal) + MIDDLE = 2, // 50% internal, 50% WLED + RIGHT = 3 // All right (WLED) } Fader; typedef struct ControlParameters { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index d8a17d13f0..ca1e9b2d59 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -166,9 +166,6 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. PatternDef gPatterns[] = { -#ifdef TESTING_PATTERNS - {tick, LongDuration}, -#else {drawNoise, {ShortDuration}}, {drawNoise, {ShortDuration}}, {drawNoise, {MediumDuration}}, @@ -184,7 +181,6 @@ PatternDef gPatterns[] = { {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, {bpm_palette, {MediumDuration, HighEnergy}} -#endif }; /* diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 44bd18553d..b2ff52d733 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -9,8 +9,8 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS -// #define TESTING_PATTERNS +#define DEFAULT_WLED_FX FX_MODE_FLOW +#define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -80,9 +80,6 @@ class VirtualStrip { if (this->isWled) { set_wled_palette(background.palette_id); - set_wled_pattern(DEFAULT_WLED_FX); - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); } } @@ -94,16 +91,22 @@ class VirtualStrip { seg.startTransition(strip.getTransition()); seg.palette = palette_id; } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); } - static void set_wled_pattern(uint8_t pattern_id) { + static void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; if (seg.mode == pattern_id) continue; seg.startTransition(strip.getTransition()); + seg.speed = speed; + seg.intensity = intensity; strip.setMode(i, pattern_id); } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); } static void set_wled_brightness(uint8_t brightness) { From d0ba71bf927a602b14003bf30c8e6d00766ed84d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 15 Aug 2022 03:47:58 -0700 Subject: [PATCH 070/263] Add WLED patterns into the list of patterns that are mixed --- usermods/Tubes/Tubes.h | 32 -------- usermods/Tubes/controller.h | 138 ++++++++++++++------------------- usermods/Tubes/debug.h | 5 +- usermods/Tubes/options.h | 17 ++++ usermods/Tubes/pattern.h | 89 +++++++++++++++++---- usermods/Tubes/virtual_strip.h | 32 ++++---- 6 files changed, 169 insertions(+), 144 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 89429aed5d..ea33e87624 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -67,38 +67,6 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { - // Perform a cross-fade between current WLED mode and the external buffer - uint8_t fade; // amount that Tubes overwrites WLED, 0-255 - switch (this->controller.options.fader) { - case AUTO: - default: - fade = sin8(millis() / 40); - break; - case LEFT: - fade = 255; - break; - case RIGHT: - fade = 0; - break; - case MIDDLE: - fade = 127; - break; - } - - if (fade > 0) { - uint16_t length = strip.getLengthTotal(); - for (int i = 0; i < length; i++) { - CRGB color1 = strip.getPixelColor(i); - CRGB color2 = controller.led_strip->getPixelColor(i); - - uint8_t r = blend8(color1.r, color2.r, fade); - uint8_t g = blend8(color1.g, color2.g, fade); - uint8_t b = blend8(color1.b, color2.b, fade); - - strip.setPixelColor(i, CRGB(r,g,b)); - } - } - // Draw effects layers over whatever WLED is doing. this->controller.overlay(); this->debug.overlay(); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 57ab0f2472..ca4a28489b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -19,15 +19,12 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define DEBUG_PATTERNS + typedef struct { bool debugging; bool power_save; uint8_t brightness; - Fader fader; // temp - - uint8_t wled_pattern; - uint8_t speed; - uint8_t intensity; uint8_t reserved[12]; } ControllerOptions; @@ -80,7 +77,8 @@ class PatternController : public MessageReceiver { VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; bool isMaster = false; - + uint16_t wled_fader = 0; + Timer graphicsTimer; Timer updateTimer; @@ -126,14 +124,6 @@ class PatternController : public MessageReceiver { this->options.power_save = false; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; -#ifdef TESTING_PATTERNS - this->options.fader = RIGHT; -#else - this->options.fader = AUTO; -#endif - this->options.wled_pattern = DEFAULT_WLED_FX; - this->options.speed = strip.getMainSegment().speed; - this->options.intensity = strip.getMainSegment().intensity; this->load_options(this->options); #ifdef USELCD @@ -186,12 +176,8 @@ class PatternController : public MessageReceiver { this->broadcast_state(); } + /* TODO: detect WLED manual overrides bool options_changed = false; - if (segment.mode != this->options.wled_pattern) { - Serial.printf("WLED FX mode: %d\n",segment.mode); - this->options.wled_pattern = segment.mode; - options_changed = true; - } if (segment.speed != this->options.speed) { Serial.printf("WLED FX speed: %d\n",segment.speed); this->options.speed = segment.speed; @@ -204,6 +190,7 @@ class PatternController : public MessageReceiver { } if (options_changed) this->broadcast_options(); + */ do_pattern_changes(); @@ -234,6 +221,30 @@ class PatternController : public MessageReceiver { } void overlay() { + static uint8_t last_fader = 0; + + // Crossfade between the custom pattern engine and WLED + uint8_t fader = this->wled_fader >> 8; + if (fader != last_fader) { + Serial.printf("fader %u ", fader); + last_fader = fader; + } + if (fader < 255) { + // Perform a cross-fade between current WLED mode and the external buffer + uint16_t length = strip.getLengthTotal(); + for (int i = 0; i < length; i++) { + CRGB c = this->led_strip->getPixelColor(i); + if (fader > 0) { + CRGB color2 = strip.getPixelColor(i); + uint8_t r = blend8(c.r, color2.r, fader); + uint8_t g = blend8(c.g, color2.g, fader); + uint8_t b = blend8(c.b, color2.b, fader); + c = CRGB(r,g,b); + } + strip.setPixelColor(i, c); + } + } + if (this->options.power_save) { // Screen door effect uint16_t length = strip.getLengthTotal(); @@ -245,8 +256,10 @@ class PatternController : public MessageReceiver { } } +#ifndef DEBUG_PATTERNS // Draw effects layers over whatever WLED is doing. this->effects->draw(&strip); +#endif } void restart_phrase() { @@ -307,7 +320,6 @@ class PatternController : public MessageReceiver { void load_options(ControllerOptions &options) { strip.setBrightness(options.brightness); - VirtualStrip::set_wled_pattern(options.wled_pattern, options.speed, options.intensity); } void load_pattern(TubeState &tube_state) { @@ -326,11 +338,15 @@ class PatternController : public MessageReceiver { // Choose the pattern to display at the next pattern cycle // Return the number of phrases until the next pattern cycle uint16_t set_next_pattern(uint16_t phrase) { - uint8_t pattern_id = random8(gPatternCount); - PatternDef def = gPatterns[pattern_id]; - if (def.control.energy > this->energy) { - pattern_id = 0; - def = gPatterns[0]; + uint8_t pattern_id; + PatternDef def; + + // Try 10 times to find a pattern that fits the current "energy" + for (int i = 0; i < 10; i++) { + pattern_id = random8(gPatternCount); + def = gPatterns[pattern_id]; + if (def.control.energy < this->energy) + break; } this->next_state.pattern_id = pattern_id; @@ -356,14 +372,15 @@ class PatternController : public MessageReceiver { void _load_palette(uint8_t palette_id) { this->current_state.palette_id = palette_id; - Serial.print(F("Change palette")); - this->background_changed(); + Serial.println("Change palette"); + VirtualStrip::set_wled_palette(palette_id); } // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { - this->next_state.palette_id = random8(gGradientPaletteCount); + // Don't select palette 1 + this->next_state.palette_id = random8(2, gGradientPaletteCount); return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); } @@ -409,6 +426,7 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; + background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; @@ -465,6 +483,8 @@ class PatternController : public MessageReceiver { } lastFrame = beat_frame; + this->wled_fader = 0; + VirtualStrip *first_strip = NULL; for (uint8_t i=0; i < NUM_VSTRIPS; i++) { VirtualStrip *vstrip = this->vstrips[i]; @@ -474,6 +494,10 @@ class PatternController : public MessageReceiver { // Remember the first strip if (first_strip == NULL) first_strip = vstrip; + + // Remember the strip that's actually WLED + if (vstrip->isWled()) + this->wled_fader = vstrip->fader; vstrip->update(beat_frame, beat_pulse); vstrip->blend(this->led_strip->leds, this->led_strip->num_leds, this->options.brightness, vstrip == first_strip); @@ -591,44 +615,6 @@ class PatternController : public MessageReceiver { this->broadcast_state(); return; - case '[': - switch (this->options.fader) { - case LEFT: - this->options.fader = AUTO; - break; - - case RIGHT: - this->options.fader = MIDDLE; - break; - - case MIDDLE: - case AUTO: - default: - this->options.fader = LEFT; - break; - } - this->broadcast_options(); - return; - - case ']': - switch (this->options.fader) { - case RIGHT: - this->options.fader = AUTO; - break; - - case LEFT: - this->options.fader = MIDDLE; - break; - - case MIDDLE: - case AUTO: - default: - this->options.fader = RIGHT; - break; - } - this->broadcast_options(); - return; - case 'm': this->next_state.pattern_phrase = 0; this->next_state.pattern_id = this->current_state.pattern_id; @@ -655,15 +641,11 @@ class PatternController : public MessageReceiver { this->broadcast_state(); return; - case 'w': - Serial.printf("Setting WLED FX to %d:128:128\n", arg >> 8); - this->options.wled_pattern = arg >> 8; - this->options.speed = 128; - this->options.intensity = 128; - this->load_options(this->options); - this->broadcast_options(); + case 'i': + Serial.printf("Reset! ID -> %03X\n", arg >> 4); + this->node->reset(arg >> 4); return; - + case 'g': for (int i=0; i< 10; i++) addGlitter(); @@ -725,13 +707,9 @@ class PatternController : public MessageReceiver { case COMMAND_OPTIONS: memcpy(&this->options, data, sizeof(this->options)); this->load_options(this->options); - Serial.printf("[debug=%d bri=%d fader=%d wled:%d:%d:%d]", + Serial.printf("[debug=%d bri=%d]", this->options.debugging, - this->options.brightness, - this->options.fader, - this->options.wled_pattern, - this->options.speed, - this->options.intensity + this->options.brightness ); return; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c5010fb8f4..9735ce357c 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -82,13 +82,14 @@ class DebugController { auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); - Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u\n\n", + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n\n", mode_name, seg.mode, palette_name, seg.palette, seg.speed, - seg.intensity + seg.intensity, + this->controller->wled_fader ); } } diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index e6c0f0eac6..ce4e18d836 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -28,6 +28,23 @@ typedef enum Fader: uint8_t { RIGHT = 3 // All right (WLED) } Fader; +uint8_t fader_to_8(Fader fader) { + switch (fader) { + case AUTO: + default: + return sin8(millis() / 40); + + case LEFT: + return 255; + + case RIGHT: + return 0; + + case MIDDLE: + return 127; + } +} + typedef struct ControlParameters { public: Duration duration=MediumDuration; diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index ca1e9b2d59..6fd2424713 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -1,6 +1,7 @@ #pragma once #include "virtual_strip.h" +#include "FX.h" void rainbow(VirtualStrip *strip) { @@ -158,7 +159,11 @@ void drawNoise(VirtualStrip *strip) } } +void draw_wled_fx(VirtualStrip *strip) { +} + typedef struct { + uint8_t wled_fx_id; BackgroundFn backgroundFn; ControlParameters control; } PatternDef; @@ -166,23 +171,77 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. PatternDef gPatterns[] = { - {drawNoise, {ShortDuration}}, - {drawNoise, {ShortDuration}}, - {drawNoise, {MediumDuration}}, - {drawNoise, {MediumDuration}}, - {drawNoise, {MediumDuration}}, - {drawNoise, {LongDuration}}, - {drawNoise, {LongDuration}}, - {confetti, {ShortDuration}}, - {confetti, {MediumDuration}}, - - {juggle, {ShortDuration}}, - {palette_wave, {ShortDuration}}, - {palette_wave, {MediumDuration}}, - {bpm_palette, {ShortDuration}}, - {bpm_palette, {MediumDuration, HighEnergy}} + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {LongDuration}}, + {0, drawNoise, {LongDuration}}, + {0, confetti, {ShortDuration}}, + {0, confetti, {MediumDuration}}, + + {0, juggle, {ShortDuration}}, + {0, palette_wave, {ShortDuration}}, + {0, palette_wave, {MediumDuration}}, + {0, bpm_palette, {ShortDuration}}, + {0, bpm_palette, {MediumDuration, HighEnergy}}, + {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 + {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 + {FX_MODE_AURORA, draw_wled_fx, {MediumDuration}}, // 38 + {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 + {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 + {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ShortDuration}},// 52 + {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 + {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 + {FX_MODE_PALETTE, draw_wled_fx, {ShortDuration}},// 65 + {FX_MODE_FIRE_2012, draw_wled_fx, {MediumDuration}}, // 66 + {FX_MODE_BPM, draw_wled_fx, {MediumDuration}}, // 68 + {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 + {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 + {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 + {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 + {FX_MODE_LAKE, draw_wled_fx, {LongDuration}}, // 75 + {FX_MODE_METEOR_SMOOTH, draw_wled_fx, {MediumDuration}}, // 77 + {FX_MODE_STARBURST, draw_wled_fx, {ShortDuration, HighEnergy}}, // 89 + {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ShortDuration}},// 90 + {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 + {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 + {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 + {FX_MODE_PACIFICA, draw_wled_fx, {LongDuration}}, // 101 + {FX_MODE_TWINKLEUP, draw_wled_fx, {LongDuration}}, // 106 + {FX_MODE_NOISEPAL, draw_wled_fx, {LongDuration}}, // 107 + {FX_MODE_PHASEDNOISE, draw_wled_fx, {MediumDuration}}, // 109 + {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}} // 110 }; /* */ const uint8_t gPatternCount = ARRAY_SIZE(gPatterns); + +/* + +WLED OK not great: +4 - Wipe random +8 - Colorloop +15 - Running +18 - Dissolve +27 - Android +36 - Sweep Random +41 - Lighthouse +44 - Tetrix +50 - Two Dots +56 - Tri Fade +67 - Color Waves --- maybe too spastic +70 - Noise 1 +73 - Noise 4 +80 - Twinklefox +104 - Sunrise +108 - Sine Wave +115 - Blends +154 - Plasmoid +157 - Noisemeter + +Jittery, why? +15 is fine, 16 is jittery +*/ \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index b2ff52d733..f19b582abb 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -10,7 +10,6 @@ #define MAX_VIRTUAL_LEDS 150 #define DEFAULT_WLED_FX FX_MODE_FLOW -#define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -18,6 +17,7 @@ typedef void (*BackgroundFn)(VirtualStrip *strip); class Background { public: BackgroundFn animate; + uint8_t wled_fx_id; uint8_t palette_id; SyncMode sync=All; }; @@ -44,8 +44,6 @@ class VirtualStrip { const static uint16_t DEF_BRIGHT = 255; public: - bool isWled = true; - CRGB leds[MAX_VIRTUAL_LEDS]; uint8_t num_leds; uint8_t brightness; @@ -78,11 +76,16 @@ class VirtualStrip { this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; - if (this->isWled) { + if (this->isWled()) { + set_wled_pattern(background.wled_fx_id, 128, 128); set_wled_palette(background.palette_id); } } + bool isWled() { + return this->background.wled_fx_id != 0; + } + static void set_wled_palette(uint8_t palette_id) { for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); @@ -197,24 +200,23 @@ class VirtualStrip { } CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { - if (this->isWled) { - Segment& segment = strip.getMainSegment(); - uint32_t color = segment.color_from_palette(c + offset, false, true, 255, brightness); - return CRGB(color); - } else { - CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; - return ColorFromPalette( palette, c + offset ); - } + Segment& segment = strip.getMainSegment(); + uint32_t color = segment.color_from_palette(c + offset, false, true, 255, brightness); + return CRGB(color); } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { return CHSV(this->hue + offset, saturation, value); } - void blend(CRGB strip[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { + void blend(CRGB output[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { if (this->fade == Dead) return; + // WLED is blended in elsewhere + if (this->isWled()) + return; + brightness = scale8(this->brightness, brightness); for (unsigned i=0; i < num_leds; i++) { @@ -224,9 +226,9 @@ class VirtualStrip { nscale8x3(c.r, c.g, c.b, brightness); nscale8x3(c.r, c.g, c.b, this->fader>>8); if (overwrite) - strip[i] = c; + output[i] = c; else - strip[i] |= c; + output[i] |= c; } } From 0fa995be8304ddee72975110f3b1282fb9245880 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 15 Aug 2022 14:18:23 -0700 Subject: [PATCH 071/263] Tuning WLED patterns --- usermods/Tubes/Tubes.h | 4 ++-- usermods/Tubes/controller.h | 38 +++++++++++++++++++++++++++++----- usermods/Tubes/debug.h | 2 +- usermods/Tubes/options.h | 9 ++++---- usermods/Tubes/pattern.h | 17 ++++++++++++--- usermods/Tubes/virtual_strip.h | 1 - 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index ea33e87624..d38d9020f1 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -68,8 +68,8 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { // Draw effects layers over whatever WLED is doing. - this->controller.overlay(); - this->debug.overlay(); + this->controller.handleOverlayDraw(); + this->debug.handleOverlayDraw(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ca4a28489b..0e74c0e292 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -142,18 +142,30 @@ class PatternController : public MessageReceiver { void do_pattern_changes() { uint16_t phrase = this->current_state.beat_frame >> 12; + bool changed = false; if (phrase >= this->next_state.pattern_phrase) { + Serial.println("Time to change pattern"); this->load_pattern(this->next_state); this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + changed = true; } if (phrase >= this->next_state.palette_phrase) { + Serial.println("Time to change palette"); this->load_palette(this->next_state); this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + changed = true; } if (phrase >= this->next_state.effect_phrase) { + Serial.println("Time to change effect"); this->load_effect(this->next_state); this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + changed = true; + } + + if (changed) { + this->next_state.print(); + Serial.println(); } } @@ -220,7 +232,7 @@ class PatternController : public MessageReceiver { #endif } - void overlay() { + void handleOverlayDraw() { static uint8_t last_fader = 0; // Crossfade between the custom pattern engine and WLED @@ -246,7 +258,7 @@ class PatternController : public MessageReceiver { } if (this->options.power_save) { - // Screen door effect + // Screen door effectn uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { @@ -335,24 +347,39 @@ class PatternController : public MessageReceiver { this->background_changed(); } + bool isShowingWled() { + return this->current_state.pattern_id >= numInternalPatterns; + } + + // For now, can't crossfade between internal and WLED patterns + // If currently running an WLED pattern, only select from internal patterns. + uint8_t get_valid_next_pattern() { + if (isShowingWled()) + return random8(0, numInternalPatterns); + return random8(0, gPatternCount); + } + // Choose the pattern to display at the next pattern cycle // Return the number of phrases until the next pattern cycle uint16_t set_next_pattern(uint16_t phrase) { uint8_t pattern_id; PatternDef def; + Serial.println("Changing next pattern"); // Try 10 times to find a pattern that fits the current "energy" for (int i = 0; i < 10; i++) { - pattern_id = random8(gPatternCount); + pattern_id = get_valid_next_pattern(); def = gPatterns[pattern_id]; if (def.control.energy < this->energy) break; } + Serial.printf("Next pattern will be %d\n", pattern_id); this->next_state.pattern_id = pattern_id; this->next_state.pattern_sync_id = this->randomSyncMode(); switch (def.control.duration) { + case ExtraShortDuration: return random8(2, 6); case ShortDuration: return random8(5,15); case MediumDuration: return random8(15,25); case LongDuration: return random8(35,45); @@ -379,8 +406,8 @@ class PatternController : public MessageReceiver { // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { - // Don't select palette 1 - this->next_state.palette_id = random8(2, gGradientPaletteCount); + // Don't select the built-in palettes + this->next_state.palette_id = random8(6, gGradientPaletteCount); return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); } @@ -415,6 +442,7 @@ class PatternController : public MessageReceiver { this->next_state.effect_params = def.params; switch (def.control.duration) { + case ExtraShortDuration: return 2; case ShortDuration: return 3; case MediumDuration: return 6; case LongDuration: return 10; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 9735ce357c..d50dc42ff3 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -94,7 +94,7 @@ class DebugController { } } - void overlay() + void handleOverlayDraw() { // Show the beat on the master OR if debugging if (this->controller->options.debugging) { diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index ce4e18d836..8ad4e5c28e 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -9,10 +9,11 @@ typedef enum SyncMode: uint8_t { } SyncMode; typedef enum Duration: uint8_t { - ShortDuration=0, - MediumDuration=10, - LongDuration=20, - ExtraLongDuration=30, + ExtraShortDuration=0, + ShortDuration=10, + MediumDuration=20, + LongDuration=30, + ExtraLongDuration=40, } Duration; typedef enum Energy: uint8_t { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 6fd2424713..77d9fb342e 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -170,6 +170,7 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. +static const uint8_t numInternalPatterns = 14; PatternDef gPatterns[] = { {0, drawNoise, {ShortDuration}}, {0, drawNoise, {ShortDuration}}, @@ -188,10 +189,13 @@ PatternDef gPatterns[] = { {0, bpm_palette, {MediumDuration, HighEnergy}}, {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 + // Make it HighEnergy? or find out why it's sometimes flashy {FX_MODE_AURORA, draw_wled_fx, {MediumDuration}}, // 38 + // TODO: Aurora is too dark? {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 - {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ShortDuration}},// 52 + {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration}},// 52 + // TODO: Kind of boring {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 {FX_MODE_PALETTE, draw_wled_fx, {ShortDuration}},// 65 @@ -199,19 +203,26 @@ PatternDef gPatterns[] = { {FX_MODE_BPM, draw_wled_fx, {MediumDuration}}, // 68 {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 + {FX_MODE_NOISE16_3, draw_wled_fx, {ShortDuration}}, // 72 {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 + // TODO: Noise3 needs to be slowed down, it's a bit spastic {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 + {FX_MODE_LAKE, draw_wled_fx, {ShortDuration}}, // 75 {FX_MODE_LAKE, draw_wled_fx, {LongDuration}}, // 75 {FX_MODE_METEOR_SMOOTH, draw_wled_fx, {MediumDuration}}, // 77 - {FX_MODE_STARBURST, draw_wled_fx, {ShortDuration, HighEnergy}}, // 89 - {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ShortDuration}},// 90 + {FX_MODE_STARBURST, draw_wled_fx, {ExtraShortDuration, HighEnergy}}, // 89 + {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ExtraShortDuration}},// 90 + // TODO: Must be set to only fire from one side {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 + {FX_MODE_PACIFICA, draw_wled_fx, {ShortDuration}}, // 101 {FX_MODE_PACIFICA, draw_wled_fx, {LongDuration}}, // 101 {FX_MODE_TWINKLEUP, draw_wled_fx, {LongDuration}}, // 106 {FX_MODE_NOISEPAL, draw_wled_fx, {LongDuration}}, // 107 {FX_MODE_PHASEDNOISE, draw_wled_fx, {MediumDuration}}, // 109 + {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {LongDuration}}, // 110 {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}} // 110 }; diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index f19b582abb..a4439a73d0 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -103,7 +103,6 @@ class VirtualStrip { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; if (seg.mode == pattern_id) continue; - seg.startTransition(strip.getTransition()); seg.speed = speed; seg.intensity = intensity; strip.setMode(i, pattern_id); From c568411dcccbc03b36216266b98157a65ca054e3 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 15 Aug 2022 17:15:01 -0700 Subject: [PATCH 072/263] Try to make some patterns a little more interesting --- usermods/Tubes/Tubes.h | 36 -------------------------------- usermods/Tubes/controller.h | 25 +++++++++++++++++----- usermods/Tubes/options.h | 30 ++++----------------------- usermods/Tubes/pattern.h | 38 +++++++++++++++++++++++++++++----- usermods/Tubes/virtual_strip.h | 5 ++++- 5 files changed, 61 insertions(+), 73 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index d38d9020f1..c7bb874763 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -72,39 +72,3 @@ class TubesUsermod : public Usermod { this->debug.handleOverlayDraw(); } }; - - - - -/* -LIST OF GOOD PATTERNS - -Aurora -Dynamic Smooth -Blends -Colortwinkles -Fireworks -Fireworks Starburst -Flow -Lake -Noise 2 -Noise 4 -Pacifica -Plasma -Ripple -Running Dual -Twinklecat -Twinkleup - -MAYBE GOOD PATTERNS -Fillnoise -Gradient -Juggle -Meteor Smooth -Palette -Phased -Saw -Sinelon Dual -Tetrix - -*/ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 0e74c0e292..73a1a2dd47 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -19,8 +19,6 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define MAX_COLOR_CHANGE_PHRASES 4 // 40 -#define DEBUG_PATTERNS - typedef struct { bool debugging; bool power_save; @@ -97,6 +95,9 @@ class PatternController : public MessageReceiver { TubeState current_state; TubeState next_state; + // When a pattern is boring, spice it up a bit with more effects + bool isBoring = false; + PatternController(uint8_t num_leds, BeatController *beats) { this->num_leds = num_leds; #ifdef USELCD @@ -164,6 +165,12 @@ class PatternController : public MessageReceiver { } if (changed) { + // For now, WLED doesn't handle transitioning pattern & palette well. + // Stagger them + if (this->next_state.pattern_phrase == this->next_state.palette_phrase) { + this->next_state.palette_phrase += random8(1,3); + } + this->next_state.print(); Serial.println(); } @@ -268,10 +275,8 @@ class PatternController : public MessageReceiver { } } -#ifndef DEBUG_PATTERNS // Draw effects layers over whatever WLED is doing. this->effects->draw(&strip); -#endif } void restart_phrase() { @@ -342,6 +347,7 @@ class PatternController : public MessageReceiver { this->current_state.pattern_phrase = tube_state.pattern_phrase; this->current_state.pattern_id = tube_state.pattern_id % gPatternCount; this->current_state.pattern_sync_id = tube_state.pattern_sync_id; + this->isBoring = gPatterns[this->current_state.pattern_id].control.energy == Boring; Serial.print(F("Change pattern ")); this->background_changed(); @@ -435,8 +441,12 @@ class PatternController : public MessageReceiver { uint16_t set_next_effect(uint16_t phrase) { uint8_t effect_num = random8(gEffectCount); + // Pick a random effect to add; boring patterns get better chance at having an effect. EffectDef def = gEffects[effect_num]; - if (def.control.energy > this->energy) + Energy maxEnergy = this->energy; + if (this->isBoring) + maxEnergy = (Energy)((uint8_t)maxEnergy + 10); + if (def.control.energy > maxEnergy) def = gEffects[0]; this->next_state.effect_params = def.params; @@ -489,6 +499,11 @@ class PatternController : public MessageReceiver { SyncMode randomSyncMode() { uint8_t r = random8(128); + + // For boring patterns, up the chance of a sync mode + if (this->isBoring) + r += 20; + if (r < 40) return SinDrift; if (r < 65) diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 8ad4e5c28e..e499def9f6 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -17,34 +17,12 @@ typedef enum Duration: uint8_t { } Duration; typedef enum Energy: uint8_t { - LowEnergy=0, - MediumEnergy=10, - HighEnergy=20, + Boring=0, + LowEnergy=10, + MediumEnergy=20, + HighEnergy=230 } Energy; -typedef enum Fader: uint8_t { - AUTO = 0, // Does a sin pattern - LEFT = 1, // All left (internal) - MIDDLE = 2, // 50% internal, 50% WLED - RIGHT = 3 // All right (WLED) -} Fader; - -uint8_t fader_to_8(Fader fader) { - switch (fader) { - case AUTO: - default: - return sin8(millis() / 40); - - case LEFT: - return 255; - - case RIGHT: - return 0; - - case MIDDLE: - return 127; - } -} typedef struct ControlParameters { public: diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 77d9fb342e..bfac7e7bf5 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -183,8 +183,8 @@ PatternDef gPatterns[] = { {0, confetti, {MediumDuration}}, {0, juggle, {ShortDuration}}, - {0, palette_wave, {ShortDuration}}, - {0, palette_wave, {MediumDuration}}, + {0, palette_wave, {ShortDuration, Boring}}, + {0, palette_wave, {MediumDuration, Boring}}, {0, bpm_palette, {ShortDuration}}, {0, bpm_palette, {MediumDuration, HighEnergy}}, {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 @@ -194,7 +194,7 @@ PatternDef gPatterns[] = { // TODO: Aurora is too dark? {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 - {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration}},// 52 + {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration, Boring}},// 52 // TODO: Kind of boring {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 @@ -253,6 +253,34 @@ WLED OK not great: 154 - Plasmoid 157 - Noisemeter -Jittery, why? -15 is fine, 16 is jittery +LIST OF GOOD PATTERNS + +Aurora +Dynamic Smooth +Blends +Colortwinkles +Fireworks +Fireworks Starburst +Flow +Lake +Noise 2 +Noise 4 +Pacifica +Plasma +Ripple +Running Dual +Twinklecat +Twinkleup + +MAYBE GOOD PATTERNS +Fillnoise +Gradient +Juggle +Meteor Smooth +Palette +Phased +Saw +Sinelon Dual +Tetrix + */ \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index a4439a73d0..85c2486640 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -77,7 +77,10 @@ class VirtualStrip { this->brightness = DEF_BRIGHT; if (this->isWled()) { - set_wled_pattern(background.wled_fx_id, 128, 128); + uint8_t wled_fx_id = background.wled_fx_id; + if (wled_fx_id < 10) + wled_fx_id = DEFAULT_WLED_FX; + set_wled_pattern(wled_fx_id, 128, 128); set_wled_palette(background.palette_id); } } From c2ea3ae1ba0deee9a7c93923435c73e6746bfff4 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 16 Aug 2022 02:38:23 -0700 Subject: [PATCH 073/263] adjust pattern balance --- usermods/Tubes/controller.h | 10 ++++----- usermods/Tubes/pattern.h | 42 ++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 73a1a2dd47..f25657f0fd 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -502,15 +502,15 @@ class PatternController : public MessageReceiver { // For boring patterns, up the chance of a sync mode if (this->isBoring) - r += 20; + r -= 20; - if (r < 40) + if (r < 30) return SinDrift; - if (r < 65) + if (r < 50) return Pulse; - if (r < 72) + if (r < 70) return Swing; - if (r < 84) + if (r < 80) return SwingDrift; return All; } diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index bfac7e7bf5..e0e6525faf 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -170,7 +170,7 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. -static const uint8_t numInternalPatterns = 14; +static const uint8_t numInternalPatterns = 24; PatternDef gPatterns[] = { {0, drawNoise, {ShortDuration}}, {0, drawNoise, {ShortDuration}}, @@ -181,33 +181,47 @@ PatternDef gPatterns[] = { {0, drawNoise, {LongDuration}}, {0, confetti, {ShortDuration}}, {0, confetti, {MediumDuration}}, + {0, juggle, {ShortDuration}}, + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {LongDuration}}, + {0, drawNoise, {LongDuration}}, + {0, confetti, {ShortDuration}}, + {0, confetti, {MediumDuration}}, {0, juggle, {ShortDuration}}, + {0, palette_wave, {ShortDuration, Boring}}, {0, palette_wave, {MediumDuration, Boring}}, {0, bpm_palette, {ShortDuration}}, {0, bpm_palette, {MediumDuration, HighEnergy}}, - {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 + {FX_MODE_FADE, draw_wled_fx, {ShortDuration, Boring}}, // 12 {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 // Make it HighEnergy? or find out why it's sometimes flashy - {FX_MODE_AURORA, draw_wled_fx, {MediumDuration}}, // 38 + {FX_MODE_AURORA, draw_wled_fx, {MediumDuration, Boring}}, // 38 // TODO: Aurora is too dark? {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration, Boring}},// 52 - // TODO: Kind of boring + {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 {FX_MODE_PALETTE, draw_wled_fx, {ShortDuration}},// 65 {FX_MODE_FIRE_2012, draw_wled_fx, {MediumDuration}}, // 66 {FX_MODE_BPM, draw_wled_fx, {MediumDuration}}, // 68 + // TODO: needs to be slowed down {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 {FX_MODE_NOISE16_3, draw_wled_fx, {ShortDuration}}, // 72 {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 // TODO: Noise3 needs to be slowed down, it's a bit spastic {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 + {FX_MODE_LAKE, draw_wled_fx, {ShortDuration}}, // 75 + {FX_MODE_LAKE, draw_wled_fx, {MediumDuration}}, // 75 {FX_MODE_LAKE, draw_wled_fx, {LongDuration}}, // 75 {FX_MODE_METEOR_SMOOTH, draw_wled_fx, {MediumDuration}}, // 77 {FX_MODE_STARBURST, draw_wled_fx, {ExtraShortDuration, HighEnergy}}, // 89 @@ -215,15 +229,26 @@ PatternDef gPatterns[] = { // TODO: Must be set to only fire from one side {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 + {FX_MODE_PLASMA, draw_wled_fx, {ShortDuration}}, // 97 {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 + {FX_MODE_PACIFICA, draw_wled_fx, {ShortDuration}}, // 101 {FX_MODE_PACIFICA, draw_wled_fx, {LongDuration}}, // 101 {FX_MODE_TWINKLEUP, draw_wled_fx, {LongDuration}}, // 106 {FX_MODE_NOISEPAL, draw_wled_fx, {LongDuration}}, // 107 {FX_MODE_PHASEDNOISE, draw_wled_fx, {MediumDuration}}, // 109 {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {MediumDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {MediumDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {LongDuration}}, // 110 - {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}} // 110 + {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}}, // 110 + {FX_MODE_FIRE_2012, draw_wled_fx, {ShortDuration}}, // 66 + {FX_MODE_FIRE_2012, draw_wled_fx, {MediumDuration}}, // 66 + {FX_MODE_PHASEDNOISE, draw_wled_fx, {ShortDuration}}, // 109 + }; /* @@ -283,4 +308,11 @@ Saw Sinelon Dual Tetrix + +Colors to fix: +72 - replace with something else +35 - drops framerate +62 - drops framerate +61 - drops framerate + */ \ No newline at end of file From c574135dfb1322060a192c72e3a982671b773840 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 17 Aug 2022 17:48:54 -0700 Subject: [PATCH 074/263] replace monochrome palettes --- wled00/FX_fcn.cpp | 2 +- wled00/palettes.h | 58 +++++++++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a003de6173..70f574b3b0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1555,7 +1555,7 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", "Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", "Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18", -"Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", +"Calpan","Calbayo","Fib53","Purple/Orange","PMH","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", "Autumn 04","Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10", "Landscape 76","Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10", "Vintage 56","Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" diff --git a/wled00/palettes.h b/wled00/palettes.h index c2ab9295bf..625675b63f 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -951,27 +951,47 @@ DEFINE_PALETTE( fib53_15_gp ) { 253, 1, 1, 1, 255, 239,241,240}; -// Gradient palette "grindylow_15_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-15.png.index.html +// Gradient palette "purple_orange_d08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/purple-orange-d08.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 12 bytes of program space. - -DEFINE_PALETTE( grindylow_15_gp ) { - 0, 101,241,105, - 127, 26,182,105, - 255, 26,151, 80}; +// Size: 64 bytes of program space. -// Gradient palette "grindylow_21_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-21.png.index.html +DEFINE_GRADIENT_PALETTE( purple_orange_d08_gp ) { + 0, 49, 26, 89, + 31, 49, 26, 89, + 31, 107, 49,106, + 63, 107, 49,106, + 63, 165, 88,127, + 95, 165, 88,127, + 95, 188,151,158, + 127, 188,151,158, + 127, 210,178,117, + 159, 210,178,117, + 159, 239,135, 37, + 191, 239,135, 37, + 191, 220, 81, 7, + 223, 220, 81, 7, + 223, 159, 37, 1, + 255, 159, 37, 1}; + +// Gradient palette "pmh_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/esdb/tn/pmh.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. +// Size: 48 bytes of program space. -DEFINE_PALETTE( grindylow_21_gp ) { - 0, 101,241,105, - 51, 60,241,240, - 128, 26,241,240, - 191, 26,182,105, - 255, 26,151, 80}; +DEFINE_PALETTE( pmh_gp ) { + 0, 255, 55,145, + 42, 255, 55,145, + 42, 255,182,145, + 84, 255,182,145, + 84, 255,255,105, + 127, 255,255,105, + 127, 171,255,174, + 170, 171,255,174, + 170, 101,255,212, + 212, 101,255,212, + 212, 171, 82,212, + 255, 171, 82,212}; // Gradient palette "konjo_08_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-08.png.index.html @@ -2045,8 +2065,8 @@ const byte* const gGradientPalettes[] PROGMEM = { calpan_18_gp, calbayo_18_gp, fib53_15_gp, - grindylow_15_gp, - grindylow_21_gp, + purple_orange_d08_gp, + pmh_gp, konjo_08_gp, konkikyo_19_gp, mccahon_16_gp, From f5e6e2e6cce0cd1444f2ee87679a4be793b3726e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 17 Aug 2022 21:05:03 -0700 Subject: [PATCH 075/263] rebalance the effects so the defaults are generally chill --- usermods/Tubes/controller.h | 16 ++++++++++++++-- usermods/Tubes/effects.h | 32 +++++++++++++++++--------------- usermods/Tubes/node.h | 2 +- usermods/Tubes/options.h | 8 ++++---- wled00/palettes.h | 2 +- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index f25657f0fd..9db41590f5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -19,6 +19,8 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define IDENTIFY_STUCK_PATTERNS + typedef struct { bool debugging; bool power_save; @@ -91,7 +93,7 @@ class PatternController : public MessageReceiver { ControllerOptions options; char key_buffer[20] = {0}; - Energy energy=LowEnergy; + Energy energy=Chill; TubeState current_state; TubeState next_state; @@ -146,19 +148,25 @@ class PatternController : public MessageReceiver { bool changed = false; if (phrase >= this->next_state.pattern_phrase) { +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change pattern"); +#endif this->load_pattern(this->next_state); this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); changed = true; } if (phrase >= this->next_state.palette_phrase) { +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change palette"); +#endif this->load_palette(this->next_state); this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); changed = true; } if (phrase >= this->next_state.effect_phrase) { +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change effect"); +#endif this->load_effect(this->next_state); this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); changed = true; @@ -306,7 +314,7 @@ class PatternController : public MessageReceiver { else if (this->current_state.bpm > 120>>8) this->energy = MediumEnergy; else - this->energy = LowEnergy; + this->energy = Chill; } void send_update() { @@ -371,7 +379,9 @@ class PatternController : public MessageReceiver { uint8_t pattern_id; PatternDef def; +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Changing next pattern"); +#endif // Try 10 times to find a pattern that fits the current "energy" for (int i = 0; i < 10; i++) { pattern_id = get_valid_next_pattern(); @@ -379,7 +389,9 @@ class PatternController : public MessageReceiver { if (def.control.energy < this->energy) break; } +#ifdef IDENTIFY_STUCK_PATTERNS Serial.printf("Next pattern will be %d\n", pattern_id); +#endif this->next_state.pattern_id = pattern_id; this->next_state.pattern_sync_id = this->randomSyncMode(); diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index eabb1c5e3f..4907bdfd4f 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -131,24 +131,26 @@ typedef struct { static const EffectDef gEffects[] = { {{None}, {LongDuration}}, - {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, - {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, - {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, - {{Flash, Brighten, Phrase}, {MediumDuration, HighEnergy}}, - {{Flash, Darken, Measure}, {ShortDuration, LowEnergy}}, - {{Glitter, Brighten, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Flash, Brighten, Beat, 40}, {MediumDuration, HighEnergy}}, + {{Flash, Darken, TwoBeats, 40}, {MediumDuration, HighEnergy}}, + {{Flash, Brighten, Measure}, {ShortDuration, MediumEnergy}}, + {{Flash, Brighten, Phrase}, {MediumDuration, MediumEnergy}}, + {{Flash, Darken, Measure}, {ShortDuration, MediumEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {ShortDuration, Chill}}, {{Glitter, Brighten, Eighth, 80}, {MediumDuration, MediumEnergy}}, {{Glitter, Brighten, Eighth, 40}, {MediumDuration, HighEnergy}}, - {{Glitter, Darken, Eighth, 40}, {MediumDuration, LowEnergy}}, + {{Glitter, Darken, Eighth, 40}, {MediumDuration, Chill}}, - {{Glitter, Draw, Eighth, 10}, {LongDuration, LowEnergy}}, - {{Glitter, Draw, Eighth, 120}, {MediumDuration, LowEnergy}}, - {{Glitter, Invert, Eighth, 40}, {ShortDuration, LowEnergy}}, - {{Beatbox2, Black}, {MediumDuration, LowEnergy}}, + {{Glitter, Draw, Eighth, 10}, {LongDuration, Chill}}, + {{Glitter, Draw, Eighth, 120}, {MediumDuration, Chill}}, + {{Glitter, Invert, Eighth, 40}, {ShortDuration, Chill}}, + {{Beatbox2, Black}, {MediumDuration, MediumEnergy}}, {{Beatbox2, Draw}, {ShortDuration, HighEnergy}}, - {{Bubble, Darken}, {MediumDuration, LowEnergy}}, - {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, - {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, - {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Bubble, Darken}, {MediumDuration, Chill}}, + {{Bubble, Brighten}, {MediumDuration, Chill}}, + {{Bubble, Brighten}, {MediumDuration, Chill}}, + {{Bubble, Invert}, {MediumDuration, Chill}}, + {{Glitter, Darken, Eighth, 120}, {MediumDuration, MediumEnergy}}, + {{Glitter, Flicker, Eighth, 120}, {MediumDuration, Chill}}, }; const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index bf1465e8e4..15e2235310 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -177,7 +177,7 @@ class LightNode { // enter or continue re-broadcasting mode. if (node->uplinkId == this->header.id || (node->uplinkId == 0 && node->id < this->header.id)) { - Serial.printf("%03X/%03X is following me\n", node->id, node->uplinkId); + Serial.printf(" %03X/%03X is following me\n", node->id, node->uplinkId); this->rebroadcastTimer.start(REBROADCAST_TIME); } } diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index e499def9f6..769c172a19 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -17,8 +17,8 @@ typedef enum Duration: uint8_t { } Duration; typedef enum Energy: uint8_t { - Boring=0, - LowEnergy=10, + Boring=0, // a "boring" pattern is slow or whatever but -needs- effects to be interesting + Chill=10, // A "chill" pattern is only slow fades, no flashes MediumEnergy=20, HighEnergy=230 } Energy; @@ -27,9 +27,9 @@ typedef enum Energy: uint8_t { typedef struct ControlParameters { public: Duration duration=MediumDuration; - Energy energy=LowEnergy; + Energy energy=Chill; - ControlParameters(Duration duration=MediumDuration, Energy energy=LowEnergy) { + ControlParameters(Duration duration=MediumDuration, Energy energy=Chill) { this->duration=duration; this->energy=energy; }; diff --git a/wled00/palettes.h b/wled00/palettes.h index 625675b63f..e664bfd9ce 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -956,7 +956,7 @@ DEFINE_PALETTE( fib53_15_gp ) { // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 64 bytes of program space. -DEFINE_GRADIENT_PALETTE( purple_orange_d08_gp ) { +DEFINE_PALETTE( purple_orange_d08_gp ) { 0, 49, 26, 89, 31, 49, 26, 89, 31, 107, 49,106, From 4ac3fa2f9457f2b52ab30a239c4a7f7da2e2d4fa Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 18 Aug 2022 00:36:42 -0700 Subject: [PATCH 076/263] New colors --- usermods/Tubes/controller.h | 7 +- usermods/Tubes/pattern.h | 2 +- wled00/FX_fcn.cpp | 13 +- wled00/const.h | 2 +- wled00/palettes.h | 266 ++++++++++++++++++++++++++++++++++-- 5 files changed, 266 insertions(+), 24 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 9db41590f5..66c937436e 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -138,6 +138,7 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; + VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX, 128, 128); this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); @@ -248,14 +249,8 @@ class PatternController : public MessageReceiver { } void handleOverlayDraw() { - static uint8_t last_fader = 0; - // Crossfade between the custom pattern engine and WLED uint8_t fader = this->wled_fader >> 8; - if (fader != last_fader) { - Serial.printf("fader %u ", fader); - last_fader = fader; - } if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer uint16_t length = strip.getLengthTotal(); diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index e0e6525faf..14946808bc 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -228,7 +228,7 @@ PatternDef gPatterns[] = { {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ExtraShortDuration}},// 90 // TODO: Must be set to only fire from one side {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 - {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 + {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration, MediumEnergy}}, // 95 {FX_MODE_PLASMA, draw_wled_fx, {ShortDuration}}, // 97 {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 70f574b3b0..191c813ac1 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1553,11 +1553,12 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Hot Lava","Fire","Hiyane","Colorfull","Magenta Evening", "Pink Purple","Sunset","Autumn","Blue/Magenta/White","Blue/Magenta/Red","Blue/Red/Yellow","Blue/Cyan/Yellow","Sunset Yellow","Cloud","Fire & Ice", "BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", -"Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", -"Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18", -"Calpan","Calbayo","Fib53","Purple/Orange","PMH","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", -"Autumn 04","Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10", -"Landscape 76","Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10", -"Vintage 56","Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" +"Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G","Wild Orange", +"Ikat Radial","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18","Calpan", +"Calbayo","Fib53","Purple/Orange","PMH","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange","Autumn 04", +"Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10","Landscape 76", +"Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10","Vintage 56", +"Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Adrift in Dreams","Set3","Pastel1","Es Rosa","Daybreak", +"Melancholiy","Xanidu","Air","Revolution","Sky05","Sky33","Sky45","Carousel","NRWC" ])====="; diff --git a/wled00/const.h b/wled00/const.h index 2cbaa10e61..eeddbdd980 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 103 // custom palette.h +#define GRADIENT_PALETTE_COUNT 116 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" diff --git a/wled00/palettes.h b/wled00/palettes.h index e664bfd9ce..42f2ffd2d6 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -1984,22 +1984,241 @@ DEFINE_PALETTE( janico_22_gp ) { 255, 194,225,255}; +// Gradient palette "Adrift_in_Dreams_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/colo/Skyblue2u/tn/Adrift_in_Dreams.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 40 bytes of program space. + +DEFINE_PALETTE( Adrift_in_Dreams_gp ) { + 0, 148,223, 77, + 51, 148,223, 77, + 51, 86,182, 89, + 102, 86,182, 89, + 102, 36,131, 72, + 153, 36,131, 72, + 153, 5, 61, 51, + 204, 5, 61, 51, + 204, 1, 15, 29, + 255, 1, 15, 29}; + + +// Gradient palette "Set3_03_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/cb/qual/tn/Set3_03.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( Set3_03_gp ) { + 0, 54,168,137, + 84, 54,168,137, + 84, 255,255,105, + 170, 255,255,105, + 170, 118,127,172, + 255, 118,127,172}; + +// Gradient palette "Pastel1_06_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/cb/qual/tn/Pastel1_06.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 48 bytes of program space. + +DEFINE_PALETTE( Pastel1_06_gp ) { + 0, 244,118, 98, + 42, 244,118, 98, + 42, 101,157,190, + 84, 101,157,190, + 84, 142,213,133, + 127, 142,213,133, + 127, 177,154,192, + 170, 177,154,192, + 170, 252,178, 87, + 212, 252,178, 87, + 212, 255,255,145, + 255, 255,255,145}; + + +// Gradient palette "es_rosa_55_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rosa/tn/es_rosa_55.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_rosa_55_gp ) { + 0, 6, 1, 2, + 101, 54, 1, 10, + 170, 15, 29, 4, + 216, 95,124, 54, + 255, 213,233,158}; + +// Gradient palette "daybreak_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/1/tn/daybreak.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( daybreak_gp ) { + 0, 1, 1, 1, + 91, 4, 11, 21, + 140, 11, 31,135, + 150, 255,255,125, + 165, 132, 18,123, + 198, 58, 92,221, + 232, 57,168,223, + 255, 255,241,242}; + + +// Gradient palette "melancholiy_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/3/tn/melancholiy.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( melancholiy_gp ) { + 0, 255,171,242, + 76, 1, 2,105, + 140, 121,136,125, + 211, 255,171,242, + 255, 1, 2,105}; + + +// Gradient palette "xanidu_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/3/tn/xanidu.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. + +DEFINE_PALETTE( xanidu_gp ) { + 0, 118,161,226, + 5, 255,255, 45, + 15, 252,203,156, + 53, 79, 1,162, + 94, 67, 1, 7, + 132, 1, 55,156, + 173, 1,127, 61, + 211, 39, 45, 72, + 255, 118,161,226}; + + +// Gradient palette "air_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/3/tn/air.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. + +DEFINE_PALETTE( air_gp ) { + 0, 252,246,103, + 84, 252,246,103, + 140, 14, 1, 91, + 155, 165,176,156, + 163, 252,246,103, + 170, 14, 1, 91, + 181, 165,176,156, + 193, 252,246,103, + 255, 252,246,103}; + + +// Gradient palette "revolution2_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/6/tn/revolution2.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 40 bytes of program space. + +DEFINE_PALETTE( revolution2_gp ) { + 0, 112, 46, 21, + 33, 101, 69, 14, + 61, 194, 74, 29, + 91, 242,115, 52, + 119, 215,211,102, + 145, 2, 2, 1, + 163, 8, 28, 46, + 186, 17, 9, 1, + 229, 215,211,102, + 255, 242,115, 52}; + + +// Gradient palette "sky_33_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rafi/tn/sky-33.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( sky_33_gp ) { + 0, 237,229,140, + 51, 227,107, 79, + 87, 155, 55, 54, + 178, 22, 28, 36, + 255, 5, 19, 31}; + +// Gradient palette "sky_45_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rafi/tn/sky-45.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( sky_45_gp ) { + 0, 249,205, 4, + 51, 255,239,123, + 87, 5,141, 85, + 178, 1, 26, 43, + 255, 0, 2, 23}; + +// Gradient palette "sky_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rafi/tn/sky-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( sky_05_gp ) { + 0, 252, 61, 2, + 25, 255,146, 4, + 63, 224,255,255, + 101, 46,114,226, + 127, 6, 40,127, + 191, 1, 3, 17, + 255, 1, 1, 4}; + + +// Gradient palette "carousel_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/carousel.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( carousel_gp ) { + 0, 2, 6, 37, + 101, 2, 6, 37, + 122, 177,121, 9, + 127, 217,149, 2, + 132, 177,121, 9, + 153, 84, 13, 36, + 255, 84, 13, 36}; + +// Gradient palette "nrwc_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/wkp/tubs/tn/nrwc.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( nrwc_gp ) { + 0, 1, 1, 1, + 25, 4, 8, 1, + 51, 1, 11, 2, + 76, 4, 36, 9, + 102, 6, 66, 18, + 127, 27, 95, 23, + 153, 82,127, 31, + 178, 197,171, 40, + 204, 133,100, 19, + 229, 97, 48, 6, + 255, 163, 55, 7}; + + + // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly // by name every time. const byte* const gGradientPalettes[] PROGMEM = { - ib_jul01_gp, //13-00 Jul01 + // starts at #13: + ib_jul01_gp, es_vintage_57_gp, es_vintage_01_gp, es_rivendell_15_gp, rgi_15_gp, retro2_16_gp, Analogous_1_gp, + + // 20 es_pinksplash_08_gp, es_pinksplash_07_gp, Coral_reef_gp, - es_ocean_breeze_068_gp, es_ocean_breeze_036_gp, departure_gp, @@ -2007,10 +2226,11 @@ const byte* const gGradientPalettes[] PROGMEM = { es_landscape_33_gp, rainbowsherbet_gp, gr65_hult_gp, + + // 30 gr64_hult_gp, GMT_drywet_gp, ib15_gp, - Fuschia_7_gp, es_emerald_dragon_08_gp, lava_gp, @@ -2018,10 +2238,11 @@ const byte* const gGradientPalettes[] PROGMEM = { haiyan_23_gp, Colorfull_gp, Magenta_Evening_gp, + + // 40 Pink_Purple_gp, Sunset_Real_gp, es_autumn_19_gp, - BlacK_Blue_Magenta_White_gp, BlacK_Magenta_Red_gp, BlacK_Red_Magenta_Yellow_gp, @@ -2029,10 +2250,11 @@ const byte* const gGradientPalettes[] PROGMEM = { Sunset_Yellow_gp, cloud_gp, fireandice_gp, + + // 50 bhw2_39_gp, rainfall_gp, tashangel_gp, - butterflytalker_gp, os250k_metres_gp, Night_Midnight_gp, @@ -2040,8 +2262,9 @@ const byte* const gGradientPalettes[] PROGMEM = { BlueSky_gp, Gold_Orange_gp, frizzell_10_gp, + + // 60 frizzell_12_gp, - fib53_18_gp, fib53_13_gp, fib53_17_gp, @@ -2050,8 +2273,9 @@ const byte* const gGradientPalettes[] PROGMEM = { Analogous_04a_gp, Cyan_Orange_Stripped_gp, Cyan_White_Green_gp, - Wild_Orange_gp, + + // 70 IKat_Radial_gp, Citrus_gp, Teal_Blue_gp, @@ -2061,8 +2285,9 @@ const byte* const gGradientPalettes[] PROGMEM = { green_purple_d07_gp, knoza_00_gp, knoza_18_gp, - calpan_18_gp, + + // 80 calbayo_18_gp, fib53_15_gp, purple_orange_d08_gp, @@ -2073,6 +2298,8 @@ const byte* const gGradientPalettes[] PROGMEM = { Pills_2_gp, Pink_Yellow_Orange_1_gp, es_autumn_04_gp, + + // 90 es_autumn_02_gp, es_candide_30_gp, es_chic_16_gp, @@ -2083,6 +2310,8 @@ const byte* const gGradientPalettes[] PROGMEM = { es_landscape_47_gp, es_landscape_10_gp, es_landscape_76_gp, + + // 100 es_landscape_61_gp, es_landscape_60_gp, es_landscape_51_gp, @@ -2093,12 +2322,29 @@ const byte* const gGradientPalettes[] PROGMEM = { es_pinksplash_05_gp, es_pinksplash_10_gp, es_vintage_56_gp, + + // 110 es_vintage_10_gp, gold_yellow_gp, radioactive_slime_gp, pastel_rainbow_gp, purple_sunset_gp, - janico_22_gp + Adrift_in_Dreams_gp, + Set3_03_gp, + Pastel1_06_gp, + es_rosa_55_gp, + daybreak_gp, + + // 120 + melancholiy_gp, + xanidu_gp, + air_gp, + revolution2_gp, + sky_05_gp, + sky_33_gp, + sky_45_gp, + carousel_gp, + nrwc_gp, /* Sunset_Real_gp, //13-00 Sunset @@ -2162,7 +2408,7 @@ const byte* const gGradientPalettes[] PROGMEM = { */ }; const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); -#define GRADIENT_PALETTE_COUNT 103 +#define GRADIENT_PALETTE_COUNT 116 #endif From a09707b8c7fcd84588a4177b3e98cff507dce542 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 18 Aug 2022 22:35:41 -0700 Subject: [PATCH 077/263] Auto-updating from a remote server --- usermods/Tubes/controller.h | 36 ++++- usermods/Tubes/led_strip.h | 17 +- usermods/Tubes/node.h | 5 +- usermods/Tubes/update_server.py | 275 ++++++++++++++++++++++++++++++++ usermods/Tubes/updater.h | 221 ++++++++++++++++++++----- 5 files changed, 496 insertions(+), 58 deletions(-) create mode 100644 usermods/Tubes/update_server.py diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 66c937436e..6190af07d9 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -70,7 +70,7 @@ class Button { class PatternController : public MessageReceiver { public: - const static int FRAMES_PER_SECOND = 100; // how often we animate, in frames per second + const static int FRAMES_PER_SECOND = 60; // how often we animate, in frames per second const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds uint8_t num_leds; @@ -79,6 +79,8 @@ class PatternController : public MessageReceiver { bool isMaster = false; uint16_t wled_fader = 0; + AutoUpdater auto_updater; + Timer graphicsTimer; Timer updateTimer; @@ -233,6 +235,7 @@ class PatternController : public MessageReceiver { this->send_update(); } + this->auto_updater.update(); } #ifdef USELCD @@ -280,6 +283,34 @@ class PatternController : public MessageReceiver { // Draw effects layers over whatever WLED is doing. this->effects->draw(&strip); + + CRGB c; + switch (this->auto_updater.status) { + case Started: + case Connected: + case Received: + c = CRGB::Yellow; + if (millis() % 1000 < 500) { + c = CRGB::Black; + } + break; + + case Failed: + c = CRGB::Red; + break; + + case Complete: + c = CRGB::Green; + break; + + case Idle: + default: + return; + } + for (int i = 0; i < 20; i++) { + strip.setPixelColor(i, c); + } + } void restart_phrase() { @@ -715,10 +746,11 @@ class PatternController : public MessageReceiver { Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); Serial.println("@ - power save mode"); + Serial.println("U - begin auto-update"); return; case 'U': - WifiUpdater().web_update(); + this->auto_updater.start(); return; case 0: diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index a4aca410c5..a15ddfaf47 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -11,8 +11,8 @@ class LEDs { CRGB leds[MAX_REAL_LEDS]; // CRGB led_array[MAX_REAL_LEDS]; - const static int FRAMES_PER_SECOND = 150; // how often we refresh the strip, in frames per second - const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we refresh the strip, in milliseconds + const static int TARGET_FRAMES_PER_SECOND = 150; + const static int TARGET_REFRESH = 1000 / TARGET_FRAMES_PER_SECOND; int num_leds; uint16_t fps = 0; @@ -33,21 +33,12 @@ class LEDs { } } - void show() { - // There's nothing to do right now, because in Tubes.h we blend the LEDs into - } - void update(bool reverse=false) { - EVERY_N_MILLISECONDS( this->REFRESH_PERIOD ) { - // Update the LEDs - if (reverse) - this->reverse(); - show(); + EVERY_N_MILLISECONDS( this->TARGET_REFRESH ) { this->fps++; } - EVERY_N_MILLISECONDS( 1000 ) { - if (this->fps < (FRAMES_PER_SECOND - 30)) { + if (this->fps < (TARGET_FRAMES_PER_SECOND - 30)) { Serial.print(this->fps); Serial.println((char *)F(" fps!")); } diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 15e2235310..d2a0bb170d 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -50,7 +50,7 @@ typedef struct { void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); -char *command_name(CommandId command) { +const char *command_name(CommandId command) { switch (command) { case COMMAND_UPDATE: return "UPDATE"; @@ -130,7 +130,10 @@ class LightNode { void configure_ap() { // Don't connect to any networks. + // strcpy(clientSSID, "Fish Tank"); + // strcpy(clientPass, "Fish Tank"); strcpy(clientSSID, ""); + strcpy(clientPass, ""); // Try to hide the access point unless this is the "root" node if (this->is_following()) { diff --git a/usermods/Tubes/update_server.py b/usermods/Tubes/update_server.py new file mode 100644 index 0000000000..3bb1e4d624 --- /dev/null +++ b/usermods/Tubes/update_server.py @@ -0,0 +1,275 @@ +#!/usr/bin/python +"""Simple HTTP Server. + +This module builds on BaseHTTPServer by implementing the standard GET +and HEAD requests in a fairly straightforward manner. + +""" +__version__ = "0.6" + +__all__ = ["SimpleHTTPRequestHandler"] + +import os +import posixpath +import BaseHTTPServer +import urllib +import cgi +import sys +import mimetypes +import zlib +from optparse import OptionParser + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +SERVER_PORT = 8000 +encoding_type = 'gzip' + +def parse_options(): + # Option parsing logic. + parser = OptionParser() + parser.add_option("-e", "--encoding", dest="encoding_type", + help="Encoding type for server to utilize", + metavar="ENCODING", default='gzip') + global SERVER_PORT + parser.add_option("-p", "--port", dest="port", default=SERVER_PORT, + help="The port to serve the files on", + metavar="ENCODING") + (options, args) = parser.parse_args() + global encoding_type + encoding_type = options.encoding_type + SERVER_PORT = int(options.port) + + if encoding_type not in ['zlib', 'deflate', 'gzip']: + sys.stderr.write("Please provide a valid encoding_type for the server to utilize.\n") + sys.stderr.write("Possible values are 'zlib', 'gzip', and 'deflate'\n") + sys.stderr.write("Usage: python GzipSimpleHTTPServer.py --encoding=\n") + sys.exit() + + +def zlib_encode(content): + zlib_compress = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS) + data = zlib_compress.compress(content) + zlib_compress.flush() + return data + + +def deflate_encode(content): + deflate_compress = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS) + data = deflate_compress.compress(content) + deflate_compress.flush() + return data + + +def gzip_encode(content): + gzip_compress = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) + data = gzip_compress.compress(content) + gzip_compress.flush() + return data + + +class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Simple HTTP request handler with GET and HEAD commands. + + This serves files from the current directory and any of its + subdirectories. The MIME type for files is determined by + calling the .guess_type() method. + + The GET and HEAD requests are identical except that the HEAD + request omits the actual contents of the file. + + """ + + server_version = "SimpleHTTP/" + __version__ + + def do_GET(self): + """Serve a GET request.""" + content = self.send_head() + if content: + self.wfile.write(content) + + def do_HEAD(self): + """Serve a HEAD request.""" + content = self.send_head() + + def send_head(self): + """Common code for GET and HEAD commands. + + This sends the response code and MIME headers. + + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + + """ + path = self.translate_path(self.path) + print("Serving path '%s'" % path) + f = None + if os.path.isdir(path): + if not self.path.endswith('/'): + # redirect browser - doing basically what apache does + self.send_response(301) + self.send_header("Location", self.path + "/") + self.end_headers() + return None + for index in "index.html", "index.htm": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return self.list_directory(path).read() + ctype = self.guess_type(path) + try: + # Always read in binary mode. Opening files in text mode may cause + # newline translations, making the actual size of the content + # transmitted *less* than the content-length! + f = open(path, 'rb') + except IOError: + self.send_error(404, "File not found") + return None + self.send_response(200) + self.send_header("Content-type", ctype) + self.send_header("Content-Encoding", encoding_type) + fs = os.fstat(f.fileno()) + raw_content_length = fs[6] + content = f.read() + + # # Encode content based on runtime arg + # if encoding_type == "gzip": + # content = gzip_encode(content) + # elif encoding_type == "deflate": + # content = deflate_encode(content) + # elif encoding_type == "zlib": + # content = zlib_encode(content) + + compressed_content_length = len(content) + f.close() + self.send_header("Content-Length", max(raw_content_length, compressed_content_length)) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.end_headers() + return content + + def list_directory(self, path): + """Helper to produce a directory listing (absent index.html). + + Return value is either a file object, or None (indicating an + error). In either case, the headers are sent, making the + interface the same as for send_head(). + + """ + try: + list = os.listdir(path) + except os.error: + self.send_error(404, "No permission to list directory") + return None + list.sort(key=lambda a: a.lower()) + f = StringIO() + displaypath = cgi.escape(urllib.unquote(self.path)) + f.write('') + f.write("\nDirectory listing for %s\n" % displaypath) + f.write("\n

Directory listing for %s

\n" % displaypath) + f.write("
\n
    \n") + for name in list: + fullname = os.path.join(path, name) + displayname = linkname = name + # Append / for directories or @ for symbolic links + if os.path.isdir(fullname): + displayname = name + "/" + linkname = name + "/" + if os.path.islink(fullname): + displayname = name + "@" + # Note: a link to a directory displays with @ and links with / + f.write('
  • %s\n' + % (urllib.quote(linkname), cgi.escape(displayname))) + f.write("
\n
\n\n\n") + length = f.tell() + f.seek(0) + self.send_response(200) + encoding = sys.getfilesystemencoding() + self.send_header("Content-type", "text/html; charset=%s" % encoding) + self.send_header("Content-Length", str(length)) + self.end_headers() + return f + + def translate_path(self, path): + """Translate a /-separated PATH to the local filename syntax. + + Components that mean special things to the local file system + (e.g. drive or directory names) are ignored. (XXX They should + probably be diagnosed.) + + """ + # abandon query parameters + path = path.split('?',1)[0] + path = path.split('#',1)[0] + path = posixpath.normpath(urllib.unquote(path)) + words = path.split('/') + words = filter(None, words) + path = os.getcwd() + "/../../.pio/build/esp32_quinled_uno" + for word in words: + drive, word = os.path.splitdrive(word) + head, word = os.path.split(word) + if word in (os.curdir, os.pardir): continue + path = os.path.join(path, word) + return path + + def guess_type(self, path): + """Guess the type of a file. + + Argument is a PATH (a filename). + + Return value is a string of the form type/subtype, + usable for a MIME Content-type header. + + The default implementation looks the file's extension + up in the table self.extensions_map, using application/octet-stream + as a default; however it would be permissible (if + slow) to look inside the data to make a better guess. + + """ + + base, ext = posixpath.splitext(path) + if ext in self.extensions_map: + return self.extensions_map[ext] + ext = ext.lower() + if ext in self.extensions_map: + return self.extensions_map[ext] + else: + return self.extensions_map[''] + + if not mimetypes.inited: + mimetypes.init() # try to read system mime.types + extensions_map = mimetypes.types_map.copy() + extensions_map.update({ + '': 'application/octet-stream', # Default + '.py': 'text/plain', + '.c': 'text/plain', + '.h': 'text/plain', + }) + + +def test(HandlerClass = SimpleHTTPRequestHandler, + ServerClass = BaseHTTPServer.HTTPServer): + """Run the HTTP request handler class. + + This runs an HTTP server on port 8000 (or the first command line + argument). + + """ + + parse_options() + + server_address = ('0.0.0.0', SERVER_PORT) + + SimpleHTTPRequestHandler.protocol_version = "HTTP/1.0" + httpd = BaseHTTPServer.HTTPServer(server_address, SimpleHTTPRequestHandler) + + sa = httpd.socket.getsockname() + print "Serving HTTP on", sa[0], "port", sa[1], "..." + httpd.serve_forever() + BaseHTTPServer.test(HandlerClass, ServerClass) + + +if __name__ == '__main__': + test() \ No newline at end of file diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 4a2a3927a2..c8295e8ffd 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -1,52 +1,160 @@ #pragma once +#include "wled.h" #include #include #include +#include "timer.h" // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { return header.substring(strlen(headerName.c_str())); } -class WifiUpdater { +typedef enum UpdateWorkflowStatus: uint8_t { + Idle=0, + Started=1, + Connected=2, + Received=4, + Complete=100, + Failed=101, +} UpdateWorkflowStatus; + +class AutoUpdater { public: - String host = "kwater.kelectronics.net"; - String bin = "/api/getfirmware/firmwareLarge.bin"; - int port = 80; + String autoUpdateSSID = ">"; + String autoUpdatePass = ">"; + String host = ""; + String bin = "/firmware.bin"; + int port = 8000; + long fileSize = 0; - void web_update() { - WiFiClient client; - long fileSize = 0; - String contentType = ""; + String _storedSSID = ""; + String _storedPass = ""; + + int progress = 0; + + WiFiClient _client; + UpdateWorkflowStatus status = Idle; + Timer timeoutTimer; + Timer progressTimer; + + void update() { + switch (this->status) { + case Complete: + case Failed: + if (this->progressTimer.ended()) + this->status = Idle; + case Idle: + return; + + case Started: + this->do_connect(); + return; - if (!WiFi.isConnected()) { - Serial.println("Not on WiFi"); + case Connected: + this->do_request(this->_client); + return; + } + } + + void start() { + if (this->status != Idle) { + log("update already in progress."); return; } - Serial.println("Connecting"); + // The auto-update process might break the current connection + _storedSSID = String(clientSSID); + _storedPass = String(clientPass); + + WLED::instance().disableWatchdog(); + + log("starting autoupdate"); + this->status = Started; + } + + void stop() { + this->_client.stop(); + + strcpy(clientSSID, _storedSSID.c_str()); + strcpy(clientPass, _storedPass.c_str()); + WLED::instance().enableWatchdog(); + + this->status = Idle; + } + + private: + void log(const char *message) { + Serial.printf("OTA: %s\n", message); + } + + void abort(const char *message) { + log(message); + this->status = Failed; + this->progressTimer.start(10000); + this->stop(); + } + + void do_connect() { + auto s = WiFi.status(); + switch (s) { + case WL_DISCONNECTED: + log("connecting to autoupdate server"); + strcpy(clientSSID, autoUpdateSSID.c_str()); + strcpy(clientPass, autoUpdatePass.c_str()); + return; + + case WL_NO_SSID_AVAIL: + abort("Invalid auto-update SSID"); + return; + + case WL_CONNECT_FAILED: + abort("Invalid auto-update password"); + return; + + case WL_CONNECTED: + if (WiFi.SSID() != autoUpdateSSID) { + log("disconnecting from WiFi"); + WiFi.disconnect(true); + apBehavior = AP_BEHAVIOR_BUTTON_ONLY; + return; + } + + log("WiFi connected"); + this->status = Connected; + return; + + default: + Serial.printf("OTA: wifi %d", (int)s); + break; + } + } + + void do_request(WiFiClient client) { if (!client.connect(host.c_str(), port)) { - Serial.println("Connect failed"); - client.stop(); + log("connect failed"); + this->stop(); return; } + log("connected"); + // Get the contents of the bin file client.print(String("GET ") + bin + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + - "Cache-Control: no-cache\r\n" + - "Connection: close\r\n\r\n"); - - unsigned long timeout = millis(); - while (client.available() == 0) { - if (millis() - timeout > 5000) { - Serial.println("Client Timeout !"); - client.stop(); + "Cache-Control: no-cache\r\n\r\n"); + + timeoutTimer.start(5000); + while (!client.available()) { + vTaskDelay( 200 ); + if (timeoutTimer.ended()) { + abort("timed out waiting for response"); return; } } - Serial.println("Reading headers"); + String contentType = ""; + while (client.available()) { // read line till /n - if the line is empty, it's the end of the headers. String line = client.readStringUntil('\n'); @@ -56,8 +164,7 @@ class WifiUpdater { // Check if the HTTP Response is 200 if (line.startsWith("HTTP/")) { if (line.indexOf("200") < 0) { - Serial.println("Got a non 200 status code"); - client.flush(); + abort(line.c_str()); return; } } @@ -65,40 +172,70 @@ class WifiUpdater { // Read the file size from Content-Length if (line.startsWith("Content-Length: ")) { fileSize = atol((getHeaderValue(line, "Content-Length: ")).c_str()); - Serial.println(line); + log(line.c_str()); } // Read the content type from Content-Type if (line.startsWith("Content-Type: ")) { contentType = getHeaderValue(line, "Content-Type: "); - Serial.println(line); + log(line.c_str()); + } + if (line.startsWith("Content-type: ")) { + contentType = getHeaderValue(line, "Content-type: "); + log(line.c_str()); } + } if (fileSize == 0 || contentType != "application/octet-stream") { - Serial.println("Must get a valid Content-Type and Content-Length header."); - client.flush(); + abort("Must get a valid Content-Type and Content-Length header."); return; } - - Serial.println("Beginning update"); + + log("found a valid OTA BIN"); + + this->status = Received; + this->do_update(client); + } + + void do_update(WiFiClient client) { if (!Update.begin(fileSize)) { - Serial.println("Cannot do the update"); + abort("no room for the update"); return; }; - Update.writeStream(client); - if (!Update.end()) { - Serial.println("Error Occurred. Error #: " + String(Update.getError())); + + this->progress = 0; + vTaskDelay(500); + uint8_t buf[4096]; + int lr; + while ((lr = client.read(buf, sizeof(buf))) > 0) { + size_t written = Update.write(buf, lr); + if (!written) + break; + + this->progress += written; + Serial.printf(" %d of %ld\n", this->progress, fileSize); + + // Give the server time to send some more data + if (!client.available()) + vTaskDelay(100); } - if (Update.isFinished()) { - Serial.println("Update successfully completed. Rebooting."); - ESP.restart(); - } else { - Serial.println("Update not finished? Something went wrong!"); + if (!Update.end()) { + Serial.println("Error #: " + String(Update.getError())); + abort("Error during streaming"); + return; } - client.flush(); - return; + if (!Update.isFinished()) { + abort("Error during finishing"); + return; + } + + log("update successfully completed. Rebooting."); + doReboot = true; + this->status = Complete; + this->progressTimer.start(10000); + this->stop(); } -}; \ No newline at end of file +}; From 54aa079c54f729a7791f94cfd17b57086308033e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 19 Aug 2022 23:30:14 -0700 Subject: [PATCH 078/263] misc fixes --- usermods/Tubes/controller.h | 25 ++++++++++++- usermods/Tubes/debug.h | 13 ++++++- usermods/Tubes/global_state.h | 1 + usermods/Tubes/node.h | 7 ++-- usermods/Tubes/pattern.h | 4 +-- usermods/Tubes/updater.h | 66 +++++++++++++++++++++-------------- 6 files changed, 83 insertions(+), 33 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 6190af07d9..38f968de8e 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -79,7 +79,7 @@ class PatternController : public MessageReceiver { bool isMaster = false; uint16_t wled_fader = 0; - AutoUpdater auto_updater; + AutoUpdater auto_updater = AutoUpdater(); Timer graphicsTimer; Timer updateTimer; @@ -732,6 +732,14 @@ class PatternController : public MessageReceiver { addGlitter(); return; + case 'W': + // Clear wifi + Serial.print("Clearing WiFi connection."); + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + WiFi.disconnect(false, true); + return; + case '?': Serial.println(F("b###.# - set bpm")); Serial.println(F("s - start phrase")); @@ -747,12 +755,17 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); Serial.println("@ - power save mode"); Serial.println("U - begin auto-update"); + Serial.println("O - offer an auto-update"); return; case 'U': this->auto_updater.start(); return; + case 'O': + broadcast_autoupdate(); + return; + case 0: // Empty command return; @@ -780,6 +793,11 @@ class PatternController : public MessageReceiver { this->node->sendCommand(COMMAND_OPTIONS, &this->options, sizeof(this->options)); } + void broadcast_autoupdate() { + AutoUpdateOffer offer; + this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.location, sizeof(this->auto_updater.location)); + } + virtual void onCommand(CommandId command, void *data) { switch (command) { case COMMAND_RESET: @@ -811,6 +829,11 @@ class PatternController : public MessageReceiver { this->beats->sync(state.bpm, state.beat_frame); return; } + + case COMMAND_UPGRADE: + memcpy((byte*)&this->auto_updater.location, (byte*)data, sizeof(AutoUpdateOffer)); + this->auto_updater.start(); + return; } Serial.printf("UNKNOWN COMMAND %02X", command); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index d50dc42ff3..4bea94a1e8 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -82,7 +82,7 @@ class DebugController { auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); - Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n\n", + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n", mode_name, seg.mode, palette_name, @@ -91,6 +91,17 @@ class DebugController { seg.intensity, this->controller->wled_fader ); + + Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", + this->controller->auto_updater.location.version, + this->controller->auto_updater.status, + this->controller->auto_updater.location.ssid, + this->controller->auto_updater.location.host[0], + this->controller->auto_updater.location.ssid[1], + this->controller->auto_updater.location.ssid[2], + this->controller->auto_updater.location.ssid[3] + ); + } } diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index a19646ec09..0e1af1c008 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,4 +57,5 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_UPGRADE = 0xE0; const static CommandId COMMAND_RESET = 0xF0; \ No newline at end of file diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index d2a0bb170d..81b5f01ed3 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -38,7 +38,7 @@ typedef enum{ ROOT=1, } MessageRecipients; -#define MESSAGE_DATA_SIZE 50 +#define MESSAGE_DATA_SIZE 64 typedef struct { MeshNodeHeader header; @@ -279,7 +279,7 @@ class LightNode { return; message->timebase = strip.timebase + millis(); -#ifdef NODE_DEBUGGING +#ifdef NODE_DEBUGGING Serial.print(" <<< "); print_message(message, 0); Serial.println(); @@ -295,7 +295,8 @@ class LightNode { void sendCommand(CommandId command, void *data, uint8_t len) { if (len > MESSAGE_DATA_SIZE) { - Serial.println("Message is too big!"); + Serial.printf("Message is too big: %d vs %d\n", + len, MESSAGE_DATA_SIZE); return; } diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 14946808bc..1e89b94f38 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -199,7 +199,7 @@ PatternDef gPatterns[] = { {0, bpm_palette, {ShortDuration}}, {0, bpm_palette, {MediumDuration, HighEnergy}}, {FX_MODE_FADE, draw_wled_fx, {ShortDuration, Boring}}, // 12 - {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 + {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration, HighEnergy}}, // 30 // Make it HighEnergy? or find out why it's sometimes flashy {FX_MODE_AURORA, draw_wled_fx, {MediumDuration, Boring}}, // 38 // TODO: Aurora is too dark? @@ -216,7 +216,7 @@ PatternDef gPatterns[] = { {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 {FX_MODE_NOISE16_3, draw_wled_fx, {ShortDuration}}, // 72 - {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 + {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration, MediumEnergy}}, // 72 // TODO: Noise3 needs to be slowed down, it's a bit spastic {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index c8295e8ffd..6d475cb969 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,6 +6,8 @@ #include #include "timer.h" +#define RELEASE_VERSION 3 + // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { return header.substring(strlen(headerName.c_str())); @@ -20,13 +22,19 @@ typedef enum UpdateWorkflowStatus: uint8_t { Failed=101, } UpdateWorkflowStatus; +typedef struct AutoUpdateOffer { + int version = RELEASE_VERSION; + char ssid[25] = "Fish Tank"; + char password[25] = "Fish Tank"; + IPAddress host = IPAddress(192,168,0,146); +} AutoUpdateOffer; + class AutoUpdater { public: - String autoUpdateSSID = ">"; - String autoUpdatePass = ">"; - String host = ""; - String bin = "/firmware.bin"; + AutoUpdateOffer location; + String path = "/firmware.bin"; int port = 8000; + long fileSize = 0; String _storedSSID = ""; @@ -46,6 +54,7 @@ class AutoUpdater { if (this->progressTimer.ended()) this->status = Idle; case Idle: + case Received: return; case Started: @@ -53,7 +62,7 @@ class AutoUpdater { return; case Connected: - this->do_request(this->_client); + this->do_request(); return; } } @@ -67,7 +76,6 @@ class AutoUpdater { // The auto-update process might break the current connection _storedSSID = String(clientSSID); _storedPass = String(clientPass); - WLED::instance().disableWatchdog(); log("starting autoupdate"); @@ -76,11 +84,10 @@ class AutoUpdater { void stop() { this->_client.stop(); - strcpy(clientSSID, _storedSSID.c_str()); strcpy(clientPass, _storedPass.c_str()); + WiFi.disconnect(false, true); WLED::instance().enableWatchdog(); - this->status = Idle; } @@ -89,6 +96,10 @@ class AutoUpdater { Serial.printf("OTA: %s\n", message); } + void log(String message) { + log(message.c_str()); + } + void abort(const char *message) { log(message); this->status = Failed; @@ -101,8 +112,8 @@ class AutoUpdater { switch (s) { case WL_DISCONNECTED: log("connecting to autoupdate server"); - strcpy(clientSSID, autoUpdateSSID.c_str()); - strcpy(clientPass, autoUpdatePass.c_str()); + strcpy(clientSSID, this->location.ssid); + strcpy(clientPass, this->location.password); return; case WL_NO_SSID_AVAIL: @@ -114,9 +125,9 @@ class AutoUpdater { return; case WL_CONNECTED: - if (WiFi.SSID() != autoUpdateSSID) { + if (WiFi.SSID() != String(this->location.ssid)) { log("disconnecting from WiFi"); - WiFi.disconnect(true); + WiFi.disconnect(false, true); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; return; } @@ -131,33 +142,36 @@ class AutoUpdater { } } - void do_request(WiFiClient client) { - if (!client.connect(host.c_str(), port)) { - log("connect failed"); - this->stop(); + void do_request() { + log("connecting"); + if (!this->_client.connect(this->location.host, this->port)) { + abort("connect failed"); return; } - log("connected"); // Get the contents of the bin file - client.print(String("GET ") + bin + " HTTP/1.1\r\n" + - "Host: " + host + "\r\n" + + log("requesting update package"); + this->_client.print(String("GET ") + this->path + " HTTP/1.1\r\n" + + "Host: " + this->location.host + "\r\n" + "Cache-Control: no-cache\r\n\r\n"); + log("awaiting response"); timeoutTimer.start(5000); - while (!client.available()) { - vTaskDelay( 200 ); + while (!this->_client.available()) { + vTaskDelay( 400 ); if (timeoutTimer.ended()) { abort("timed out waiting for response"); return; } + log("waiting..."); } String contentType = ""; - while (client.available()) { + log("reading response"); + while (this->_client.available()) { // read line till /n - if the line is empty, it's the end of the headers. - String line = client.readStringUntil('\n'); + String line = this->_client.readStringUntil('\n'); line.trim(); if (!line.length()) break; @@ -195,7 +209,7 @@ class AutoUpdater { log("found a valid OTA BIN"); this->status = Received; - this->do_update(client); + this->do_update(this->_client); } void do_update(WiFiClient client) { @@ -206,7 +220,7 @@ class AutoUpdater { this->progress = 0; vTaskDelay(500); - uint8_t buf[4096]; + uint8_t buf[2048]; int lr; while ((lr = client.read(buf, sizeof(buf))) > 0) { size_t written = Update.write(buf, lr); @@ -232,8 +246,8 @@ class AutoUpdater { return; } - log("update successfully completed. Rebooting."); doReboot = true; + log("update successfully completed. Rebooting."); this->status = Complete; this->progressTimer.start(10000); this->stop(); From 1455918d77179d17d4ec7af10e0ad2a9526be2bc Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 20 Aug 2022 22:17:20 -0700 Subject: [PATCH 079/263] Manual mode and basic settings --- usermods/Tubes/controller.h | 106 +++++++++++++++++++++++++-------- usermods/Tubes/debug.h | 14 ++++- usermods/Tubes/node.h | 7 ++- usermods/Tubes/pattern.h | 4 ++ usermods/Tubes/virtual_strip.h | 16 +++-- 5 files changed, 110 insertions(+), 37 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 38f968de8e..77090ef167 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -9,10 +9,12 @@ #include "pattern.h" #include "palettes.h" #include "effects.h" +#include "led_strip.h" #include "global_state.h" #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; +const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 @@ -77,12 +79,16 @@ class PatternController : public MessageReceiver { VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; bool isMaster = false; + uint8_t paletteOverride = 0; + uint8_t patternOverride = 0; uint16_t wled_fader = 0; AutoUpdater auto_updater = AutoUpdater(); Timer graphicsTimer; Timer updateTimer; + Timer paletteOverrideTimer; + Timer patternOverrideTimer; #ifdef USELCD Lcd *lcd; @@ -126,9 +132,12 @@ class PatternController : public MessageReceiver { { this->node->setup(); this->isMaster = isMaster; - this->options.power_save = false; + this->options.power_save = true; this->options.debugging = false; - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + if (isMaster) + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + else + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; this->load_options(this->options); #ifdef USELCD @@ -140,7 +149,8 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; - VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX, 128, 128); + VirtualStrip::set_wled_palette(0); // Default palette + VirtualStrip::set_wled_pattern(0, 128, 128); // Default pattern this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); @@ -197,30 +207,56 @@ class PatternController : public MessageReceiver { // Update patterns to the beat this->update_beat(); - // Detect manual overrides & update the current state to match. Segment& segment = strip.getMainSegment(); - if (segment.palette != this->current_state.palette_id) { - Serial.printf("Palette override = %d\n",segment.palette); - this->next_state.palette_phrase = 0; - this->next_state.palette_id = segment.palette; - this->broadcast_state(); + + // Detect manual overrides & update the current state to match. + uint8_t expected_palette = this->current_state.palette_id; + if (this->paletteOverride && !this->paletteOverrideTimer.ended()) { + expected_palette = this->paletteOverride; + } + if (segment.palette != expected_palette) { + if (segment.palette == 0) { + if (this->paletteOverride) { + // Selecting Default = allow Tubes to control palette. + Serial.println("Turning off WLED control of palette."); + this->paletteOverride = 0; + this->paletteOverrideTimer.stop(); + VirtualStrip::set_wled_palette(this->current_state.palette_id); + } + } else { + Serial.println("WLED has control of palette."); + this->paletteOverride = segment.palette; + this->paletteOverrideTimer.start(300000); // 5 minutes of manual control + } + } + + uint8_t wled_pattern_id = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (wled_pattern_id < 10) { + wled_pattern_id = DEFAULT_WLED_FX; } - /* TODO: detect WLED manual overrides - bool options_changed = false; - if (segment.speed != this->options.speed) { - Serial.printf("WLED FX speed: %d\n",segment.speed); - this->options.speed = segment.speed; - options_changed = true; + uint8_t expected_pattern = wled_pattern_id; + if (this->patternOverride && !this->patternOverrideTimer.ended()) { + expected_pattern = this->patternOverride; } - if (segment.intensity != this->options.intensity) { - Serial.printf("WLED FX intensity: %d\n",segment.intensity); - this->options.intensity = segment.intensity; - options_changed = true; + if (segment.mode != expected_pattern) { + if (segment.mode == 0) { + if (this->patternOverride) { + // Selecting Default = allow Tubes to control patterns. + Serial.println("Turning off WLED control of patterns."); + this->patternOverrideTimer.stop(); + this->patternOverride = 0; + transitionDelay = 8000; // Back to long transitions + VirtualStrip::set_wled_pattern(wled_pattern_id, 128, 128); + } + } else { + Serial.println("WLED has control of patterns."); + this->patternOverride = segment.mode; + this->patternOverrideTimer.start(300000); // 5 minutes of manual control + + transitionDelay = 500; // Quick transitions + } } - if (options_changed) - this->broadcast_options(); - */ do_pattern_changes(); @@ -234,9 +270,9 @@ class PatternController : public MessageReceiver { if (!this->node->is_following() || random(0, 5) == 0) { this->send_update(); } + } this->auto_updater.update(); - } #ifdef USELCD if (this->lcd->active) { @@ -252,6 +288,11 @@ class PatternController : public MessageReceiver { } void handleOverlayDraw() { + // In manual mode WLED is always active + if (this->patternOverride) { + this->wled_fader = 0xFFFF; + } + // Crossfade between the custom pattern engine and WLED uint8_t fader = this->wled_fader >> 8; if (fader < 255) { @@ -282,7 +323,10 @@ class PatternController : public MessageReceiver { } // Draw effects layers over whatever WLED is doing. - this->effects->draw(&strip); + // But not in manual (WLED) mode + if (!this->patternOverride) { + this->effects->draw(&strip); + } CRGB c; switch (this->auto_updater.status) { @@ -444,7 +488,9 @@ class PatternController : public MessageReceiver { this->current_state.palette_id = palette_id; Serial.println("Change palette"); - VirtualStrip::set_wled_palette(palette_id); + if (!this->paletteOverride) { + VirtualStrip::set_wled_palette(palette_id); + } } // Choose the palette to display at the next palette cycle @@ -502,7 +548,12 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (this->patternOverride) { + // Don't update WLED + background.wled_fx_id = 0; + } else { + background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; + } background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; @@ -648,6 +699,9 @@ class PatternController : public MessageReceiver { case '@': this->setPowerSave(!this->options.power_save); break; + case '~': + ESP.restart(); + break; case '-': b = this->options.brightness; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 4bea94a1e8..a15998cd30 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -82,15 +82,23 @@ class DebugController { auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); - Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n", + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u", mode_name, seg.mode, palette_name, seg.palette, seg.speed, - seg.intensity, - this->controller->wled_fader + seg.intensity ); + if (this->controller->patternOverride) { + Serial.printf(" (PATTERN %d)", this->controller->patternOverride); + } else { + Serial.printf(" at %d", this->controller->wled_fader); + } + if (this->controller->paletteOverride) { + Serial.printf(" (PALETTE %d)", this->controller->paletteOverride); + } + Serial.println(); Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", this->controller->auto_updater.location.version, diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 81b5f01ed3..0fc0ba05c2 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -129,11 +129,14 @@ class LightNode { } void configure_ap() { +#ifdef DEFAULT_WIFI + strcpy(clientSSID, DEFAULT_WIFI); + strcpy(clientPass, DEFAULT_WIFI_PASSWORD); +#else // Don't connect to any networks. - // strcpy(clientSSID, "Fish Tank"); - // strcpy(clientPass, "Fish Tank"); strcpy(clientSSID, ""); strcpy(clientPass, ""); +#endif // Try to hide the access point unless this is the "root" node if (this->is_following()) { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 1e89b94f38..a9cd57a3c5 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -297,6 +297,10 @@ Running Dual Twinklecat Twinkleup +AUDIOREACTIVE +Midnoise +GravCenter + MAYBE GOOD PATTERNS Fillnoise Gradient diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 85c2486640..bb17ff3b36 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -9,7 +9,7 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -#define DEFAULT_WLED_FX FX_MODE_FLOW +#define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -76,11 +76,11 @@ class VirtualStrip { this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; - if (this->isWled()) { - uint8_t wled_fx_id = background.wled_fx_id; - if (wled_fx_id < 10) - wled_fx_id = DEFAULT_WLED_FX; - set_wled_pattern(wled_fx_id, 128, 128); + // If this is the strip that's controlling WLED, update WLED's vars + if (background.wled_fx_id) { + set_wled_pattern(background.wled_fx_id, 128, 128); + } + if (background.palette_id) { set_wled_palette(background.palette_id); } } @@ -102,6 +102,10 @@ class VirtualStrip { } static void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { + // Never set it to one of the default patterns + if (pattern_id < 10) + pattern_id = DEFAULT_WLED_FX; + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; From f286a308d1b593baea2ce58ad6b9b23345b986a6 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 21 Aug 2022 01:56:09 -0700 Subject: [PATCH 080/263] Rewrite the transitions engine and fix manual overrides --- usermods/Tubes/Tubes.h | 1 + usermods/Tubes/controller.h | 172 +++++++++++++++++++++------------ usermods/Tubes/virtual_strip.h | 42 -------- 3 files changed, 110 insertions(+), 105 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index c7bb874763..c5bda68df5 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -43,6 +43,7 @@ class TubesUsermod : public Usermod { fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time strip.setTargetFps(60); + strip.setCCT(100); // Start timing globalTimer.setup(); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 77090ef167..ace882fa83 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,6 +15,8 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; +#define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE + #define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 @@ -149,8 +151,8 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; - VirtualStrip::set_wled_palette(0); // Default palette - VirtualStrip::set_wled_pattern(0, 128, 128); // Default pattern + this->set_wled_palette(0); // Default palette + this->set_wled_pattern(0, 128, 128); // Default pattern this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); @@ -197,6 +199,41 @@ class PatternController : public MessageReceiver { } } + void set_palette_override(uint8_t value) { + if (value == this->paletteOverride) + return; + + this->paletteOverride = value; + if (value) { + Serial.println("WLED has control of palette."); + this->paletteOverrideTimer.start(300000); // 5 minutes of manual control + } else { + Serial.println("Turning off WLED control of palette."); + this->paletteOverrideTimer.stop(); + this->set_wled_palette(this->current_state.palette_id); + } + } + + void set_pattern_override(uint8_t value, uint8_t auto_mode) { + if (value == DEFAULT_WLED_FX && !this->patternOverride) + return; + if (value == this->patternOverride) + return; + + this->patternOverride = value; + if (value) { + Serial.println("WLED has control of patterns."); + this->patternOverrideTimer.start(300000); // 5 minutes of manual control + transitionDelay = 500; // Short transitions + } else { + Serial.println("Turning off WLED control of patterns."); + this->patternOverrideTimer.stop(); + transitionDelay = 8000; // Back to long transitions + + this->set_wled_pattern(auto_mode, 128, 128); + } + } + void update() { this->read_keys(); @@ -210,52 +247,19 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); // Detect manual overrides & update the current state to match. - uint8_t expected_palette = this->current_state.palette_id; - if (this->paletteOverride && !this->paletteOverrideTimer.ended()) { - expected_palette = this->paletteOverride; - } - if (segment.palette != expected_palette) { - if (segment.palette == 0) { - if (this->paletteOverride) { - // Selecting Default = allow Tubes to control palette. - Serial.println("Turning off WLED control of palette."); - this->paletteOverride = 0; - this->paletteOverrideTimer.stop(); - VirtualStrip::set_wled_palette(this->current_state.palette_id); - } - } else { - Serial.println("WLED has control of palette."); - this->paletteOverride = segment.palette; - this->paletteOverrideTimer.start(300000); // 5 minutes of manual control - } + if (this->paletteOverride && this->paletteOverrideTimer.ended()) { + this->set_palette_override(0); + } else if (segment.palette != this->current_state.palette_id) { + this->set_palette_override(segment.palette); } - uint8_t wled_pattern_id = gPatterns[this->current_state.pattern_id].wled_fx_id; - if (wled_pattern_id < 10) { - wled_pattern_id = DEFAULT_WLED_FX; - } - - uint8_t expected_pattern = wled_pattern_id; - if (this->patternOverride && !this->patternOverrideTimer.ended()) { - expected_pattern = this->patternOverride; - } - if (segment.mode != expected_pattern) { - if (segment.mode == 0) { - if (this->patternOverride) { - // Selecting Default = allow Tubes to control patterns. - Serial.println("Turning off WLED control of patterns."); - this->patternOverrideTimer.stop(); - this->patternOverride = 0; - transitionDelay = 8000; // Back to long transitions - VirtualStrip::set_wled_pattern(wled_pattern_id, 128, 128); - } - } else { - Serial.println("WLED has control of patterns."); - this->patternOverride = segment.mode; - this->patternOverrideTimer.start(300000); // 5 minutes of manual control - - transitionDelay = 500; // Quick transitions - } + uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (wled_mode < 10) + wled_mode = DEFAULT_WLED_FX; + if (this->patternOverride && this->patternOverrideTimer.ended()) { + this->set_pattern_override(0, wled_mode); + } else if (segment.mode != wled_mode) { + this->set_pattern_override(segment.mode, wled_mode); } do_pattern_changes(); @@ -481,16 +485,8 @@ class PatternController : public MessageReceiver { return; this->current_state.palette_phrase = tube_state.palette_phrase; - this->_load_palette(tube_state.palette_id); - } - - void _load_palette(uint8_t palette_id) { - this->current_state.palette_id = palette_id; - - Serial.println("Change palette"); - if (!this->paletteOverride) { - VirtualStrip::set_wled_palette(palette_id); - } + this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; + set_wled_palette(this->current_state.palette_id); } // Choose the palette to display at the next palette cycle @@ -548,21 +544,66 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - if (this->patternOverride) { - // Don't update WLED - background.wled_fx_id = 0; - } else { - background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; - } + background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; - // re-use virtual strips to prevent heap fragmentation + // Use one of the virtual strips to render the patterns. + // A WLED-based pattern exists on the virtual strip, but causes + // it to do nothing since WLED merging happens in handleOverlayDraw. + // Reuse virtual strips to prevent heap fragmentation for (uint8_t i = 0; i < NUM_VSTRIPS; i++) { this->vstrips[i]->fadeOut(); } this->vstrips[this->next_vstrip]->load(background); this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; + + set_wled_pattern(background.wled_fx_id, 128, 128); + set_wled_palette(background.palette_id); + } + + void set_wled_palette(uint8_t palette_id) { + if (this->paletteOverride) + palette_id = this->paletteOverride; + + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (seg.palette == palette_id) continue; + if (!seg.isActive()) continue; + seg.startTransition(strip.getTransition()); + seg.palette = palette_id; + } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } + + void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { + if (this->patternOverride) + pattern_id = this->patternOverride; + else if (pattern_id == 0) + pattern_id = DEFAULT_WLED_FX; // Never set it to solid + + // When fading IN, make the pattern transition immediate if possible + bool fadeIn = this->wled_fader < 2000; + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (!seg.isActive()) continue; + if (seg.mode == pattern_id) continue; + if (fadeIn) { + seg.startTransition(0); + } else { + seg.startTransition(strip.getTransition()); + } + seg.speed = speed; + seg.intensity = intensity; + strip.setMode(i, pattern_id); + } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } + + static void set_wled_brightness(uint8_t brightness) { + strip.setBrightness(brightness); } void setBrightness(uint8_t brightness) { @@ -702,6 +743,11 @@ class PatternController : public MessageReceiver { case '~': ESP.restart(); break; + case 'M': + // Cancel any overrides + this->paletteOverrideTimer.stop(); + this->patternOverrideTimer.stop(); + break; case '-': b = this->options.brightness; diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index bb17ff3b36..e227610de0 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -75,54 +75,12 @@ class VirtualStrip { this->fader = 0; this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; - - // If this is the strip that's controlling WLED, update WLED's vars - if (background.wled_fx_id) { - set_wled_pattern(background.wled_fx_id, 128, 128); - } - if (background.palette_id) { - set_wled_palette(background.palette_id); - } } bool isWled() { return this->background.wled_fx_id != 0; } - static void set_wled_palette(uint8_t palette_id) { - for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { - Segment& seg = strip.getSegment(i); - if (seg.palette == palette_id) continue; - if (!seg.isActive()) continue; - seg.startTransition(strip.getTransition()); - seg.palette = palette_id; - } - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); - } - - static void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { - // Never set it to one of the default patterns - if (pattern_id < 10) - pattern_id = DEFAULT_WLED_FX; - - for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { - Segment& seg = strip.getSegment(i); - if (!seg.isActive()) continue; - if (seg.mode == pattern_id) continue; - seg.speed = speed; - seg.intensity = intensity; - strip.setMode(i, pattern_id); - } - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); - } - - static void set_wled_brightness(uint8_t brightness) { - strip.setBrightness(brightness); - } - - void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) { if (this->fade == Dead) From 9af34b9a0adc20baa6697054d60b6f020cce00fe Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 21 Aug 2022 04:28:30 -0700 Subject: [PATCH 081/263] More effects --- usermods/Tubes/effects.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 4907bdfd4f..bdb59fe957 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -145,10 +145,13 @@ static const EffectDef gEffects[] = { {{Glitter, Draw, Eighth, 120}, {MediumDuration, Chill}}, {{Glitter, Invert, Eighth, 40}, {ShortDuration, Chill}}, {{Beatbox2, Black}, {MediumDuration, MediumEnergy}}, + {{Beatbox2, Black}, {MediumDuration, MediumEnergy}}, {{Beatbox2, Draw}, {ShortDuration, HighEnergy}}, {{Bubble, Darken}, {MediumDuration, Chill}}, {{Bubble, Brighten}, {MediumDuration, Chill}}, {{Bubble, Brighten}, {MediumDuration, Chill}}, + {{Bubble, Darken}, {MediumDuration, Chill}}, + {{Bubble, Invert}, {MediumDuration, Chill}}, {{Bubble, Invert}, {MediumDuration, Chill}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, MediumEnergy}}, {{Glitter, Flicker, Eighth, 120}, {MediumDuration, Chill}}, From 6bc5ce25be528ee36a09eef4bbae130f42cc9ac4 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 22 Aug 2022 04:25:02 -0700 Subject: [PATCH 082/263] Clean up unused --- usermods/Tubes/TimeSync/counter.h | 409 ----------------------------- usermods/Tubes/TimeSync/sync.h | 180 ------------- usermods/Tubes/TimeSync/timesync.h | 397 ---------------------------- usermods/Tubes/bluetooth.h | 4 +- usermods/Tubes/firmware.sh | 69 +++++ wled00/json.cpp | 33 +++ 6 files changed, 104 insertions(+), 988 deletions(-) delete mode 100644 usermods/Tubes/TimeSync/counter.h delete mode 100644 usermods/Tubes/TimeSync/sync.h delete mode 100644 usermods/Tubes/TimeSync/timesync.h create mode 100755 usermods/Tubes/firmware.sh diff --git a/usermods/Tubes/TimeSync/counter.h b/usermods/Tubes/TimeSync/counter.h deleted file mode 100644 index a35187de1c..0000000000 --- a/usermods/Tubes/TimeSync/counter.h +++ /dev/null @@ -1,409 +0,0 @@ -/** \file - \brief Counter Math - \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of Counter nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -/** \page Counter Math - - Represents an unsigned counter that can roll over from its maximum value - back to zero. A common example is a 32-bit timestamp from GetTickCount() - on Windows, which can roll-over and cause software bugs despite testing. - This also provides compression for counters. - - This class provides: - + Counters of 2 bits through 64 bits e.g. 24-bit counters - + Increment/decrement by 1 or a constant - + Safe comparison operator overloads - + Compression/decompression via truncation - + Unit tested software -*/ - -#include -#include - -// Compiler-specific force inline keyword -#if defined(_MSC_VER) - #define COUNTER_FORCE_INLINE inline __forceinline -#else // _MSC_VER - #define COUNTER_FORCE_INLINE inline __attribute__((always_inline)) -#endif // _MSC_VER - - -//------------------------------------------------------------------------------ -// Counter - -template class Counter -{ -public: - typedef Counter ThisType; - typedef T ValueType; - typedef typename std::make_signed::type SignedType; - - static const unsigned kBits = TkBits; ///< Number of data bits - static const T kMSB = (T)1 << (TkBits - 1); ///< Most Significant Bit - - /// Generate a bit mask with 1s for each data bit - /// The mask gets optimized away when compiler optimizations are enabled - static const T kMask = static_cast(-(int64_t)1) >> (sizeof(T) * 8 - kBits); - - /// Counter value - T Value; - - - //-------------------------------------------------------------------------- - // Assignment - - Counter(T value = 0) - : Value(value & kMask) - { - } - Counter(const ThisType& b) - : Value(b.Value) - { - } - - ThisType& operator=(T value) - { - Value = value & kMask; - return *this; - } - ThisType& operator=(const ThisType b) - { - Value = b.Value; - return *this; - } - - - //-------------------------------------------------------------------------- - // Accessors - - /// Get current value - COUNTER_FORCE_INLINE T ToUnsigned() const - { - return Value; - } - - - //-------------------------------------------------------------------------- - // Increment - - /// Pre-increment - COUNTER_FORCE_INLINE ThisType& operator++() - { - Value = (Value + 1) & kMask; - return *this; - } - - /// Pre-decrement - COUNTER_FORCE_INLINE ThisType& operator--() - { - Value = (Value - 1) & kMask; - return *this; - } - - /// Post-increment - COUNTER_FORCE_INLINE ThisType operator++(int) - { - const T oldValue = Value; - Value = (Value + 1) & kMask; - return oldValue; - } - - /// Post-decrement - COUNTER_FORCE_INLINE ThisType operator--(int) - { - const T oldValue = Value; - Value = (Value - 1) & kMask; - return oldValue; - } - - - //-------------------------------------------------------------------------- - // Addition - - COUNTER_FORCE_INLINE ThisType& operator+=(const ThisType b) - { - Value = (Value + b.Value) & kMask; - return *this; - } - - COUNTER_FORCE_INLINE ThisType operator+(const ThisType b) const - { - return Value + b.Value; // Implicit mask - } - - COUNTER_FORCE_INLINE ThisType& operator-=(const ThisType b) - { - Value = (Value - b.Value) & kMask; - return *this; - } - - COUNTER_FORCE_INLINE ThisType operator-(const ThisType b) const - { - return Value - b.Value; // Implicit mask - } - - - //-------------------------------------------------------------------------- - // Comparison - // We can only compare counters of the same type - - COUNTER_FORCE_INLINE bool operator==(const ThisType b) const - { - return Value == b.Value; - } - COUNTER_FORCE_INLINE bool operator!=(const ThisType b) const - { - return Value != b.Value; - } - COUNTER_FORCE_INLINE bool operator>=(const ThisType b) const - { - const T d = static_cast(Value - b.Value) & kMask; - return d < kMSB; - } - COUNTER_FORCE_INLINE bool operator<(const ThisType b) const - { - const T d = static_cast(Value - b.Value) & kMask; - return d >= kMSB; - } - COUNTER_FORCE_INLINE bool operator<=(const ThisType b) const - { - const T d = static_cast(b.Value - Value) & kMask; - return d < kMSB; - } - COUNTER_FORCE_INLINE bool operator>(const ThisType b) const - { - const T d = static_cast(b.Value - Value) & kMask; - return d >= kMSB; - } - - - //-------------------------------------------------------------------------- - // Counter Compression (Truncation) and Re-Expansion - - /** - These routines will truncate a counter to a smaller number of bits, - and later expand the smaller value back into the original counter value - provided with a reference counter. For example a 64-bit timestamp can - be compressed down to 24 bits, sent over a network, and then expanded - back to the original value given a local time at the receiver. - - This assumes that counters are counting up and that roll-over can only - happen one time. If a counter rolls over twice, then the resulting - expanded counter value will be incorrect. - */ - - /// Compress to smaller counter by truncating - template - COUNTER_FORCE_INLINE SmallerT Truncate() const - { - static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); - - // Truncate to smaller type - return SmallerT(static_cast(Value)); - } - - /// Expand from truncated counter - /// Bias > 0 can be used to accept values farther in the past - /// Bias < 0 can be used to accept values farther in the future - template - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncatedWithBias( - const ThisType recent, - const SmallerT smaller, - const SignedType bias) - { - static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); - - /** - The bits in the smaller counter were all truncated from the correct - value, so what needs to be determined now is all the higher bits. - - Examples: - - Recent Smaller => Expanded - ------ ------- -------- - 0x100 0xff 0x0ff - 0x16f 0x7f 0x17f - 0x17f 0x6f 0x16f - 0x1ff 0xa0 0x1a0 - 0x1ff 0x01 0x201 - - The choice to make is between -1, 0, +1 for the next bit position. - - Since we have no information about the high bits, it should be - sufficient to compare the recent low bits with the smaller value - in order to decide which one is correct: - - 00 - ff = -ff -> -1 - 6f - 7f = -10 -> 0 - 7f - 6f = +10 -> 0 - ff - a0 = +5f -> 0 - ff - 01 = +fe -> +1 - */ - - // First insert the low bits to get the default result - ThisType result = smaller.ToUnsigned() | (recent.ToUnsigned() & ~static_cast(SmallerT::kMask)); - - // Grab the low bits of the recent counter - const T recentLow = recent.ToUnsigned() & SmallerT::kMask; - - // If recent - smaller would be negative: - if (recentLow < smaller.ToUnsigned()) - { - // If it is large enough to roll back a MSB: - const T absDiff = smaller.ToUnsigned() - recentLow; - if (absDiff >= static_cast(SmallerT::kMSB - bias)) - result -= static_cast(SmallerT::kMSB) << 1; - } - else - { - // If it is large enough to roll ahead a MSB: - const T absDiff = recentLow - smaller.ToUnsigned(); - if (absDiff > static_cast(SmallerT::kMSB + bias)) - result += static_cast(SmallerT::kMSB) << 1; - } - - return result; - } - - /// Expand from truncated counter without any bias - template - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const SmallerT smaller) - { - static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); - - ValueType smallerMSB = smaller.Value & SmallerT::kMSB; - SignedType smallerSigned = smaller.Value - (smallerMSB << 1); - - auto smallRecent = static_cast(recent.Value & SmallerT::kMask); - - // Signed gap = partial - prev - auto gap = static_cast(smallerSigned - smallRecent) & SmallerT::kMask; - - ValueType gapMSB = gap & SmallerT::kMSB; - SignedType gapSigned = gap - (gapMSB << 1); - - // Result = recent + gap - return recent.Value + gapSigned; - } - - // Template specialization to optimize cases where the word size matches - // the field size. Otherwise the extra sign handling above is not elided - // by the compiler's optimizer: - - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const Counter smaller) - { - static_assert(32 < kBits, "Smaller type must be smaller"); - - const int32_t gap = static_cast(smaller.Value - static_cast(recent.Value)); - return recent + gap; - } - - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const Counter smaller) - { - static_assert(16 < kBits, "Smaller type must be smaller"); - - const int16_t gap = static_cast( smaller.Value - static_cast(recent.Value) ); - return recent + gap; - } - - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const Counter smaller) - { - static_assert(8 < kBits, "Smaller type must be smaller"); - - const int8_t gap = static_cast(smaller.Value - static_cast(recent.Value)); - return recent + gap; - } - - - static_assert(std::is_pod::value, "Type must be a plain-old data type"); - static_assert(std::is_unsigned::value, "Type must be unsigned"); - static_assert(TkBits > 0, "Invalid input"); - static_assert(sizeof(T) * 8 >= TkBits, "Base type is not wide enough"); - - static_assert(kMask != 0, "bugcheck"); - static_assert(kMSB < kMask, "bugcheck"); - static_assert(kMSB != 0, "bugcheck"); -}; - - -/// Convenience declarations -typedef Counter Counter64; -typedef Counter Counter56; -typedef Counter Counter48; -typedef Counter Counter40; -typedef Counter Counter32; -typedef Counter Counter24; -typedef Counter Counter16; -typedef Counter Counter10; -typedef Counter Counter8; -typedef Counter Counter4; - -static_assert(sizeof(Counter8) == 1, "Unexpected padding"); -static_assert(sizeof(Counter16) == 2, "Unexpected padding"); -static_assert(sizeof(Counter32) == 4, "Unexpected padding"); -static_assert(sizeof(Counter64) == 8, "Unexpected padding"); - -/** - CounterExpand() - - This is a common utility function that expands a 1-7 byte truncated - counter back into a 64-bit (8 byte) counter, based on the largest - counter value seen so far. - - Preconditions: bytes > 0 && bytes < 8 -*/ -COUNTER_FORCE_INLINE Counter64 CounterExpand( - uint64_t largest, - uint64_t partial, - unsigned bytes) -{ - switch (bytes) - { - case 1: return Counter64::ExpandFromTruncated(largest, Counter8((uint8_t)partial)); - case 2: return Counter64::ExpandFromTruncated(largest, Counter16((uint16_t)partial)); - case 3: return Counter64::ExpandFromTruncated(largest, Counter24((uint32_t)partial)); - case 4: return Counter64::ExpandFromTruncated(largest, Counter32((uint32_t)partial)); - case 5: return Counter64::ExpandFromTruncated(largest, Counter40(partial)); - case 6: return Counter64::ExpandFromTruncated(largest, Counter48(partial)); - case 7: return Counter64::ExpandFromTruncated(largest, Counter56(partial)); - default: - break; - } - - return 0; -} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/sync.h b/usermods/Tubes/TimeSync/sync.h deleted file mode 100644 index da8cb7234f..0000000000 --- a/usermods/Tubes/TimeSync/sync.h +++ /dev/null @@ -1,180 +0,0 @@ -/** \file - \brief TimeSync: Time Synchronization - \copyright Copyright (c) 2017-2019 Christopher A. Taylor. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of TimeSync nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "timesync.h" - - -//------------------------------------------------------------------------------ -// WindowedMinTS24 - -void WindowedMinTS24::Update( - Counter24 value, - uint64_t timestamp, - const uint64_t windowLengthTime) -{ - const Sample sample(value, timestamp); - - // On the first sample, new best sample, or if window length has expired: - if (!IsValid() || - value <= Samples[0].Value || - Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime)) - { - Reset(sample); - return; - } - - // Insert the new value into the sorted array - if (value <= Samples[1].Value) - Samples[2] = Samples[1] = sample; - else if (value <= Samples[2].Value) - Samples[2] = sample; - - // Expire best if it has been the best for a long time - if (Samples[0].TimeoutExpired(sample.Timestamp, windowLengthTime)) - { - // Also expire the next best if needed - if (Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime)) - { - Samples[0] = Samples[2]; - Samples[1] = sample; - } - else - { - Samples[0] = Samples[1]; - Samples[1] = Samples[2]; - } - Samples[2] = sample; - return; - } - - // Quarter of window has gone by without a better value - Use the second-best - if (Samples[1].Value == Samples[0].Value && - Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime / 4)) - { - Samples[2] = Samples[1] = sample; - return; - } - - // Half the window has gone by without a better value - Use the third-best one - if (Samples[2].Value == Samples[1].Value && - Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime / 2)) - { - Samples[2] = sample; - } -} - - -//------------------------------------------------------------------------------ -// TimeSynchronizer - -void TimeSynchronizer::OnPeerMinDeltaTS24(Counter24 minDeltaTS24) -{ - LastFC_MinDeltaTS24 = minDeltaTS24; - GotPeerUpdate = true; - - Recalculate(); -} - -unsigned TimeSynchronizer::OnAuthenticatedDatagramTimestamp( - Counter24 remoteSendTS24, - uint64_t localRecvUsec) -{ - const Counter24 localTS24 = (uint32_t)(localRecvUsec >> kTime23LostBits); - - // OWD_i + ClockDelta(L-R)_i = Local Receive Time - Remote Send Time - const Counter24 deltaTS24 = localTS24 - remoteSendTS24; - - WindowedMinTS24Deltas.Update(deltaTS24, localRecvUsec, kDriftWindowUsec); - - Recalculate(); - - // Estimated one-way-delay (OWD) for this datagram in microseconds. - // This does not include processing time only network delay and perhaps - // some delays from the Operating System when it is heavily loaded. - // Set to 0 if trip time is not available - unsigned networkTripUsec = 0; - - if (IsSynchronized()) - { - // This is equivalent to the shortest RTT/2 seen so far by any pair of packets, - // meaning that it is the average of the upstream and downstream OWD. - networkTripUsec = GetMinimumOneWayDelayUsec(); - - // While the OWD is an estimate, the relative delay between that - // smallest packet pair and the current datagram is actually precise: - const Counter24 minDeltaTS24 = GetMinDeltaTS24(); - if (deltaTS24 > minDeltaTS24) - { - const Counter24 relativeTS24 = deltaTS24 - minDeltaTS24; - networkTripUsec += relativeTS24.ToUnsigned() << kTime23LostBits; - } - - // What should happen here is if the delay of each packet varies a lot, then we should - // get pretty accurate OWD for each packet. But if the variance is low and the delays - // for upstream and downstream are asymmetric, then it will underestimate the OWD by - // half of that asymmetry. Hopefully this inaccuracy won't cause problems.. - } - - return networkTripUsec; -} - -void TimeSynchronizer::Recalculate() -{ - if (!WindowedMinTS24Deltas.IsValid() || !GotPeerUpdate) - return; - - // min(OWD_i) + ClockDelta(L-R)_i - const Counter24 minRecvDeltaTS24 = WindowedMinTS24Deltas.GetBest(); - - // min(OWD_j) + ClockDelta(R-L)_j - const Counter24 minSendDeltaTS24 = LastFC_MinDeltaTS24; - - // Assume min(OWD_i) = min(OWD_j): - // min(OWD) ~= (min(OWD_j) + min(OWD_i)) / 2 - const Counter23 minOWD_TS23 = (minSendDeltaTS24 + minRecvDeltaTS24).ToUnsigned() >> 1; - - // Assume ClockDelta(R-L)_j = -ClockDelta(L-R)_i: - // ClockDelta(R-L) ~= (ClockDelta(R-L)_j - ClockDelta(L-R)_i) / 2 - const Counter23 clockDelta_TS23 = (minSendDeltaTS24 - minRecvDeltaTS24).ToUnsigned() >> 1; - - // Calculate the time delta in microseconds - RemoteTimeDeltaUsec = clockDelta_TS23.ToUnsigned() << kTime23LostBits; - - // Calculate the minimum OWD, which may go negative and blow up.. - uint32_t min_owd_usec = minOWD_TS23.ToUnsigned() << kTime23LostBits; - - // If the implied subtraction went negative, correct to zero: - static const uint32_t kSignRolloverThreshold = (1 << 22) << kTime23LostBits; - if (min_owd_usec >= kSignRolloverThreshold) { - min_owd_usec = 0; - } - MinimumOneWayDelayUsec = min_owd_usec; - - Synchronized = true; -} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/timesync.h b/usermods/Tubes/TimeSync/timesync.h deleted file mode 100644 index 48a04f5b31..0000000000 --- a/usermods/Tubes/TimeSync/timesync.h +++ /dev/null @@ -1,397 +0,0 @@ -/** \file - \brief TimeSync: Time Synchronization - \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of TimeSync nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "counter.h" - -#include - -/** - Time Synchronization Protocol - - -- Motivation: - - Time synchronization is an important core component of an rUDP library, - enabling multiple advantages over reliable UDP libraries without: - - (1) This specific (new) time synchronization works better over cellular - networks than PTP/NTP. - - (2) The API provides time synchronization as a feature for applications, - enabling millisecond-accurate dead reckoning for video games, and - 16-microsecond-accurate timing for scientific applications with 2-3 bytes. - - (3) Peer2Peer NAT hole-punch can be optimized because it can use time - synchronization to initiate probes simultaneously on both peers. - - (4) Delay-based Congestion Control systems should use One Way Delay (OWD) - on each packet as a signal, which allows it to e.g. avoid causing latency - in realtime games while delivering a file transfer in the background. - All existing Delay-based CC algorithms use differential OWD rather than - proper time synchronization. By adding time synchronization, CC becomes - robust to changes in the base OWD as the end-points remain synced. - - -- Background: - - Network time synchronization can be done two ways: - (a) Broadcast - Infeasible on the Internet and so not used. - (b) Assuming that the link is symmetric, and trusting Min(RTT/2) = OWD. - Meaning that existing network time synchronization protocols like NTP and - PTP work by sending multiple probes, and then taking the probe with the - smallest round trip time to be the best data to use in the set of probes. - - Time synchronization at higher resolutions needs to be performed constantly - because clocks drift at a rate of about 1 millisecond per 10 seconds. - So, a common choice for reliable UDP game protocols is to probe for time - synchronization purposes (running NTP all the time) at a fixed interval of - e.g. 5 to 10 seconds. - - The disadvantage of all of these existing approaches is that they only use - a finite number of probes, and all probes may be slightly skewed, - especially when jitter is present, or cross-traffic, or self-congestion - from a file transfer. - - For cellular networks, existing time synchronization approaches provide - degraded results due to the 4-10 millisecond jitter on every packet. - "An End-to-End Measurement Study of Modern Cellular Data Networks" (2014) - https://www.comp.nus.edu.sg/~bleong/publications/pam14-ispcheck.pdf - - when a link is asymmetrical there is no known practical method for - performing time synchronization between two peers that meet on the Internet, - so in that case a best effort is done, and at least it will be consistent. - - -- Algorithm: - - This TimeSync protocol overcomes this jitter problem by using a massive - number of probes (every packet is a probe), greatly increasing the odds - of a minimal RTT probe. - - How it works is that every packet has a 3 byte microsecond timestamp on it, - large enough to prevent roll-over. Both sides record the receive time of - each packet as early as possible, and then throw the packet onto another - thread to process, so that the network delay is more accurate and each - client of a server can run in parallel. - - While each packet is being processed the difference in send and receive - times are compared with prior such differences. And a windowed minimum - of these differences is updated. Periodically this minimum difference - is reported to the remote peer so that both sides have both the minimum - (outgoing - incoming) difference and the minimum (incoming - outgoing) - difference. - - These minimum differences correspond to the shortest trips each way. - Effectively, it turns every single packet into a time synchronization - probe, guaranteeing that it gets the best result possible. - - -- The (Simple) Math for TimeSync: - - We measure (Smallest C2S Delta) and (Smallest S2C Delta) - through the per-packet timestamps. - - C2S = (Smallest C2S Delta) - = (Server Time @ Client Send Time) + (C2S Propagation Delay) - (Client Send Time) - = (C2S Propagation Delay) + (Clock Delta) - - S2C = (Smallest S2C Delta) - = (Client Time @ Server Send Time) + (S2C Propagation Delay) - (Server Send Time) - = (S2C Propagation Delay) - (Clock Delta) - - Such that (Propagation Delay) for each direction is minimized - independently as described previously. - - We want to solve for (Clock Delta) but there is a problem: - - Note that in the definition of C2S and S2C there are three unknowns and - only two measurements. To resolve this problem we make the assumption - that the min. propagation delays are almost the same in each direction. - - And so: - - (S2C Propagation Delay) approx equals (C2S Propagation Delay). - - Thus we can simply write: - - (Clock Delta) = (C2S - S2C) / 2 - - This gives us 23 bits of the delta between clocks, since the division - by 2 (right shift) pulls in an unknown high bit. The effect of any - link asymmetry is halved as a side effect, helping to minimize it. - - -- The (Simple) Math for Network Trip Time: - - It also provides a robust estimate of the "speed of light" between two - network hosts (minimal one-way delay). This information is then used to - calculate the network trip time for every packet that arrives: - - Let DD = Distance from the current packet timestamp difference and minimal. - DD = (Packet Receive - Packet Send) - Min(Packet Receive - Packet Send) - Packet trip time = (Minimal one-way delay) + DD. -*/ - - -//------------------------------------------------------------------------------ -// Constants - -/// Default One Way Delay (OWD) to return before time sync completes -static const uint32_t kDefaultOWDUsec = 200 * 1000; ///< 200 ms - -/// Number of bits removed from the low end of the microsecond timestamp -static const unsigned kTime23LostBits = 3; - -/// Largest 23-bit counter difference value considered positive -static const unsigned kTime23Bias = 0x200000; - -/// Error bound for 23-bit timestamps <= 8*2-1 = 15 microseconds -static const unsigned kTime23ErrorBound = (1 << kTime23LostBits) * 2 - 1; - -/// Number of bits removed from the low end of the microsecond timestamp -static const unsigned kTime16LostBits = 9; - -/// Largest 16-bit counter difference value considered positive -static const unsigned kTime16Bias = 0x4000; - -/// Error bound for 16-bit timestamps <= 512*2-1 = 1.023 milliseconds -static const unsigned kTime16ErrorBound = (1 << kTime16LostBits) * 2 - 1; - -/// Window size for WindowedMinTS24Deltas. -/// Since clocks drift over time, eventually old measurements must be ignored. -/// This is also the longest that a timing measurement will affect time synch. -/// Assumes that clocks drift 1 millisecond every 10 seconds -static const uint64_t kDriftWindowUsec = 10 * 1000 * 1000; ///< 10 seconds - - -//------------------------------------------------------------------------------ -// Types - -/// Use Counter23::Decompress to expand back to 64-bit counters -typedef Counter Counter23; - - -//------------------------------------------------------------------------------ -// WindowedMinTS24 - -/// Windowed minimum in TS24 units -class WindowedMinTS24 -{ -public: - WindowedMinTS24() {} - - struct Sample - { - /// Sample value - Counter24 Value; - - /// Timestamp of data collection - uint64_t Timestamp; - - - /// Default values and initializing constructor - explicit Sample(Counter24 value = 0, uint64_t timestamp = 0) - : Value(value) - , Timestamp(timestamp) - { - } - - /// Check if a timeout expired - inline bool TimeoutExpired(uint64_t now, uint64_t timeout) - { - return (uint64_t)(now - Timestamp) > timeout; - } - }; - - - /// Number of samples collected - static const unsigned kSampleCount = 3; - - /// Sorted samples from smallest to largest - Sample Samples[kSampleCount]; - - - /// Are there any samples? - inline bool IsValid() const - { - return Samples[0].Value != 0; ///< ish - } - - /// Get smallest sample - inline Counter24 GetBest() const - { - return Samples[0].Value; - } - - /// Reset samples - inline void Reset(const Sample sample = Sample()) - { - Samples[0] = Samples[1] = Samples[2] = sample; - } - - /// Update minimum with new value - void Update( - Counter24 value, - uint64_t timestamp, - const uint64_t windowLengthTime); -}; - - -//------------------------------------------------------------------------------ -// TimeSynchronizer - -class TimeSynchronizer -{ -public: - /** - OnPeerMinDeltaTS24() - - Call this when the peer provides its latest 24-bit MinDeltaTS24 value. - The peer should do this periodically, ideally faster in the first minute - and then settling down to once every 2 seconds or so. This can be used - as a keep-alive for example. - - minDeltaTS24: Provide the low 24 bits of the peer's delta value. - */ - void OnPeerMinDeltaTS24(Counter24 minDeltaTS24); - - /// Convert local time in microseconds to a 24-bit datagram timestamp - static inline uint32_t LocalTimeToDatagramTS24(uint64_t localUsec) - { - return (uint32_t)(localUsec >> kTime23LostBits) & 0x00ffffff; - } - - /** - OnAuthenticatedDatagramTimestamp() - - Call this when a datagram arrives with an attached 24-bit timestamp. - Ideally every UDP/IP datagram we receive will have a timestamp. - It is recommended to check if the datagram is from the peer before - accepting the timestamp on the datagram. - - remoteSendTS24: The 24-bit timestamp attached to an incoming datagram. - localRecvUsec: A recent timestamp in microsecond units. - - Returns estimated one way delay (OWD) in microseconds for this datagram. - Returns 0 if OWD is unavailable. - */ - unsigned OnAuthenticatedDatagramTimestamp( - Counter24 remoteSendTS24, - uint64_t localRecvUsec); - - - /// Get the minimum TS24 (receipt - send) delta seen in the past interval - inline Counter24 GetMinDeltaTS24() const - { - return WindowedMinTS24Deltas.GetBest(); - } - - /// Is time synchronized? - inline bool IsSynchronized() const - { - return Synchronized; - } - - /// Get the minimum one-way delay seen so far. - /// This is equivalent to the shortest RTT/2 seen so far by any pair of packets, - /// meaning that it is the average of the upstream and downstream OWD. - inline uint32_t GetMinimumOneWayDelayUsec() const - { - return MinimumOneWayDelayUsec; - } - - /// Returns 16-bit remote time field to send in a packet - inline uint16_t ToRemoteTime16(uint64_t localUsec) - { - if (!Synchronized) { - return 0; - } - - const uint16_t localTS16 = (uint16_t)(localUsec >> kTime16LostBits); - const uint16_t deltaTS16 = (uint16_t)(RemoteTimeDeltaUsec >> kTime16LostBits); - - return localTS16 + deltaTS16; - } - - /// Returns local time given local time from packet - inline uint64_t FromLocalTime16( - uint64_t localUsec, - Counter16 timestamp16) - { - return Counter64::ExpandFromTruncatedWithBias( - localUsec >> kTime16LostBits, - timestamp16, - kTime16Bias).ToUnsigned() << kTime16LostBits; - } - - /// Returns 23-bit remote time field to send in a packet - inline uint32_t ToRemoteTime23(uint64_t localUsec) - { - if (!Synchronized) { - return 0; - } - - const Counter23 localTS23 = (uint32_t)(localUsec >> kTime23LostBits); - const Counter23 deltaTS23 = RemoteTimeDeltaUsec >> kTime23LostBits; - - return (localTS23 + deltaTS23).ToUnsigned(); - } - - /// Returns local time given remote time from packet - inline uint64_t FromLocalTime23( - uint64_t localUsec, - Counter23 timestamp23) - { - return Counter64::ExpandFromTruncatedWithBias( - localUsec >> kTime23LostBits, - timestamp23, - kTime23Bias).ToUnsigned() << kTime23LostBits; - } - -protected: - /// Synchronized? - std::atomic Synchronized = ATOMIC_VAR_INIT(false); - - /// Calculated delta = (Remote time - Local time) - std::atomic RemoteTimeDeltaUsec = ATOMIC_VAR_INIT(0); ///< usec - - /// Calculated minimum OWD - std::atomic MinimumOneWayDelayUsec = ATOMIC_VAR_INIT(kDefaultOWDUsec); ///< in usec - - /// Windowed minimum value for received packet timestamp deltas - /// Keep track of the smallest (receipt - send) time delta seen so far - WindowedMinTS24 WindowedMinTS24Deltas; ///< in Timestamp24 units - - /// Keep a copy of the last MinDeltaUsec from the flow control data from peer - Counter24 LastFC_MinDeltaTS24 = 0; - - /// Is peer update received yet? - bool GotPeerUpdate = false; - - - /// Recalculate MinimumOneWayDelayUsec and RemoteTimeDeltaUsec - void Recalculate(); -}; \ No newline at end of file diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 4c11d89872..0322264939 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -1,5 +1,7 @@ #pragma once +// THIS FILE ISN'T USED ANY MORE + #include #include #include "global_state.h" @@ -7,8 +9,6 @@ #include "node.h" -// #include "TimeSync/sync.h" - #define MAX_CONNECTED_CLIENTS 3 #define DATA_UPDATE_SERVICE "D00B" diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh new file mode 100755 index 0000000000..5c983acc4a --- /dev/null +++ b/usermods/Tubes/firmware.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Updates new boards (which start as broadcasting on WLED-AP) to custom firmware +# Will update as many boards as are plugged in, one at a time. + +WLEDPATH=../../build_output/firmware +ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools + +update_firmware() { + echo "Updating firmware via OTA" + curl -s -F "update=@../../build_output/firmware/esp32_quinled_uno.bin" $1/update >/dev/null + echo "Updated; wait..." + sleep 5 +} + +update_config() { + curl -s http://$1/upload -F "data=@cfg-tmp.json;filename=/cfg.json" >/dev/null + curl -s http://$1/reset >/dev/null +} + +connect() { + if ! networksetup -getairportnetwork en0 | grep $1 + then + echo "Connecting to $1" + networksetup -setairportnetwork en0 $1 "wled1234" + echo "Connected; wait..." + sleep 5 + fi +} + +start() { + connect "WLED-AP" + + ping -c 1 -t 2 $1 >/dev/null + PINGRESULT=$? + if [ $PINGRESULT -eq 0 ]; then + update_firmware $1 + else + echo Missing $1 + fi + return 1 +} + +# TODO: extract the config-creation from here +update_one() { + ping -c 1 -t 2 $1 >/dev/null + PINGRESULT=$? + if [ $PINGRESULT -eq 0 ]; then + echo Updating $1 + curl -s http://$1/presets.json -o presets-tmp.json >/dev/null + curl -s http://$1/cfg.json -o cfg-tmp.json >/dev/null + + update_firmware $1 + curl -s -F "data=@cfg-tmp.json;filename=/cfg.json" http://$1/upload >/dev/null + curl -s -F "data=@presets-tmp.json;filename=/presets.json" http://$1/upload >/dev/null + curl -s http://$1/reset >/dev/null + rm presets-tmp.json + rm cfg-tmp.json + return 0 + else + echo Missing $1 + fi + return 1 +} + +while : +do + start 4.3.2.1 +done diff --git a/wled00/json.cpp b/wled00/json.cpp index c7d583e161..c9e5eacf3d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -178,6 +178,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) seg.startTransition(strip.getTransition()); // set effect transitions //seg.markForReset(); seg.mode = fx; +<<<<<<< HEAD } } @@ -201,6 +202,38 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) if (sOpt != seg.palette) { if (strip.paletteFade && !seg.transitional) seg.startTransition(strip.getTransition()); seg.palette = sOpt; +======= + // load default values from effect string if effect is selected without + // any other effect parameter (i.e. effect clicked in UI) + if ( elem[F("sx")].isNull() + && elem[F("ix")].isNull() + && elem["pal"].isNull() + && elem[F("c1")].isNull() + && elem[F("c2")].isNull() + && elem[F("c3")].isNull() + && false // TEMPORARILY DISABLED, I don't want it in my code- SteveE + ) + { + int16_t sOpt; + sOpt = extractModeDefaults(fx, SET_F("sx")); if (sOpt >= 0) seg.speed = sOpt; + sOpt = extractModeDefaults(fx, SET_F("ix")); if (sOpt >= 0) seg.intensity = sOpt; + sOpt = extractModeDefaults(fx, SET_F("c1")); if (sOpt >= 0) seg.custom1 = sOpt; + sOpt = extractModeDefaults(fx, SET_F("c2")); if (sOpt >= 0) seg.custom2 = sOpt; + sOpt = extractModeDefaults(fx, SET_F("c3")); if (sOpt >= 0) seg.custom3 = sOpt; + sOpt = extractModeDefaults(fx, SET_F("mp12")); if (sOpt >= 0) seg.map1D2D = sOpt & 0x03; + sOpt = extractModeDefaults(fx, SET_F("ssim")); if (sOpt >= 0) seg.soundSim = sOpt & 0x07; + sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) seg.reverse = (bool)sOpt; + sOpt = extractModeDefaults(fx, SET_F("mi")); if (sOpt >= 0) seg.mirror = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, SET_F("rY")); if (sOpt >= 0) seg.reverse_y = (bool)sOpt; + sOpt = extractModeDefaults(fx, SET_F("mY")); if (sOpt >= 0) seg.mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "pal"); + if (sOpt >= 0 && sOpt < strip.getPaletteCount() + strip.customPalettes.size()) { + if (sOpt != seg.palette) { + if (strip.paletteFade && !seg.transitional) seg.startTransition(strip.getTransition()); + seg.palette = sOpt; + } + } +>>>>>>> 4a7b0f57 (Clean up unused) } } } From 7070bfe336658da3a65d1f003a6e9af8c29dfe4e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 22 Aug 2022 18:47:35 -0700 Subject: [PATCH 083/263] Fix OTA updates --- usermods/Tubes/controller.h | 14 ++++-- usermods/Tubes/default_config.json | 1 + usermods/Tubes/firmware.sh | 70 +++++++++++++++++------------- usermods/Tubes/node.h | 10 +---- usermods/Tubes/update_server.py | 2 +- usermods/Tubes/updater.h | 44 ++++++++++++++----- wled00/button.cpp | 6 ++- 7 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 usermods/Tubes/default_config.json diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ace882fa83..f7a37be542 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -332,6 +332,16 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } + drawOTAOverlay(); + + // When AP mode is on, make sure it's obvious + if (apActive) { + strip.setPixelColor(0, CRGB::Purple); + strip.setPixelColor(1, CRGB::Black); + } + } + + void drawOTAOverlay() { CRGB c; switch (this->auto_updater.status) { case Started: @@ -358,7 +368,6 @@ class PatternController : public MessageReceiver { for (int i = 0; i < 20; i++) { strip.setPixelColor(i, c); } - } void restart_phrase() { @@ -931,8 +940,7 @@ class PatternController : public MessageReceiver { } case COMMAND_UPGRADE: - memcpy((byte*)&this->auto_updater.location, (byte*)data, sizeof(AutoUpdateOffer)); - this->auto_updater.start(); + this->auto_updater.start((AutoUpdateOffer*)data); return; } diff --git a/usermods/Tubes/default_config.json b/usermods/Tubes/default_config.json new file mode 100644 index 0000000000..55c340b5f7 --- /dev/null +++ b/usermods/Tubes/default_config.json @@ -0,0 +1 @@ +{"rev":[1,0],"vid":2208180,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"Tube","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":1000,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0}]},"com":[],"btn":{"max":4,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8},"tr":{"mode":true,"dur":80,"pal":1},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"twice":false,"grp":1}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"addr":1,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0]},"hue":{"en":false,"id":1,"iv":25,"recv":{"on":true,"bri":true,"col":true},"ip":[0,0,0,0]},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":false,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1,-1,-1]},"cfg":{"squelch":10,"gain":60,"AGC":0},"dynamics":{"Limiter":true,"Rise":80,"Fall":1400},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 5c983acc4a..38e7d26d4a 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -6,30 +6,41 @@ WLEDPATH=../../build_output/firmware ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools +upload_firmware() { + echo "Uploading firmware" + sftp control@brcac.com </dev/null + curl -s http://$1/reset >/dev/null +} + update_firmware() { echo "Updating firmware via OTA" curl -s -F "update=@../../build_output/firmware/esp32_quinled_uno.bin" $1/update >/dev/null echo "Updated; wait..." sleep 5 -} - -update_config() { - curl -s http://$1/upload -F "data=@cfg-tmp.json;filename=/cfg.json" >/dev/null - curl -s http://$1/reset >/dev/null + echo "Updating configuration via OTA" + update_config $1 } connect() { - if ! networksetup -getairportnetwork en0 | grep $1 + if ! networksetup -getairportnetwork en0 | grep "$1" then echo "Connecting to $1" - networksetup -setairportnetwork en0 $1 "wled1234" + networksetup -setairportnetwork en0 "$1" "$2" echo "Connected; wait..." sleep 5 fi } -start() { - connect "WLED-AP" +update_one() { + connect "$2" "$3" ping -c 1 -t 2 $1 >/dev/null PINGRESULT=$? @@ -41,29 +52,28 @@ start() { return 1 } -# TODO: extract the config-creation from here -update_one() { - ping -c 1 -t 2 $1 >/dev/null - PINGRESULT=$? - if [ $PINGRESULT -eq 0 ]; then - echo Updating $1 - curl -s http://$1/presets.json -o presets-tmp.json >/dev/null - curl -s http://$1/cfg.json -o cfg-tmp.json >/dev/null +update_batch() { + airport -s | grep WLED | cut -c23-32 | while read line + do + if [ "$line" == "WLED-AP" ]; then + update_one 4.3.2.1 "$line" "wled1234" + else + update_one 4.3.2.1 "$line" "WledWled" + fi + done +} - update_firmware $1 - curl -s -F "data=@cfg-tmp.json;filename=/cfg.json" http://$1/upload >/dev/null - curl -s -F "data=@presets-tmp.json;filename=/presets.json" http://$1/upload >/dev/null - curl -s http://$1/reset >/dev/null - rm presets-tmp.json - rm cfg-tmp.json - return 0 +process() { + if [ "$1" == "upload" ]; then + upload_firmware + elif [ "$1" == "batch" ]; then + update_batch else - echo Missing $1 + while : + do + update_one 4.3.2.1 "WLED-AP" "wled1234" + done fi - return 1 } -while : -do - start 4.3.2.1 -done +process "$@" diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 0fc0ba05c2..07bc07c415 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -138,14 +138,8 @@ class LightNode { strcpy(clientPass, ""); #endif - // Try to hide the access point unless this is the "root" node - if (this->is_following()) { - sprintf(apSSID, "WLED %03X F", this->header.id); - } else { - sprintf(apSSID, "WLED %03X", this->header.id); - } - strcpy(apPass, "WledWled"); - apBehavior = AP_BEHAVIOR_NO_CONN; + // By default, we don't want these visible. + apBehavior = AP_BEHAVIOR_BUTTON_ONLY; // Must press button for 6 seconds to get AP } void start() { diff --git a/usermods/Tubes/update_server.py b/usermods/Tubes/update_server.py index 3bb1e4d624..2cea859c69 100644 --- a/usermods/Tubes/update_server.py +++ b/usermods/Tubes/update_server.py @@ -266,7 +266,7 @@ def test(HandlerClass = SimpleHTTPRequestHandler, httpd = BaseHTTPServer.HTTPServer(server_address, SimpleHTTPRequestHandler) sa = httpd.socket.getsockname() - print "Serving HTTP on", sa[0], "port", sa[1], "..." + print("Serving HTTP on", sa[0], "port", sa[1], "...") httpd.serve_forever() BaseHTTPServer.test(HandlerClass, ServerClass) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 6d475cb969..b048bc3812 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -31,9 +31,12 @@ typedef struct AutoUpdateOffer { class AutoUpdater { public: - AutoUpdateOffer location; + AutoUpdateOffer current_version; + + // For now, hardcode it all + String host_name = "brcac.com"; String path = "/firmware.bin"; - int port = 8000; + int port = 80; long fileSize = 0; @@ -67,17 +70,24 @@ class AutoUpdater { } } - void start() { + void start(AutoUpdateOffer *new_version) { if (this->status != Idle) { log("update already in progress."); return; } + if (new_version->version <= current_version.version) { + log("don't need to update to that version."); + return; + } + // The auto-update process might break the current connection _storedSSID = String(clientSSID); _storedPass = String(clientPass); WLED::instance().disableWatchdog(); - + + memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); + log("starting autoupdate"); this->status = Started; } @@ -111,9 +121,11 @@ class AutoUpdater { auto s = WiFi.status(); switch (s) { case WL_DISCONNECTED: - log("connecting to autoupdate server"); - strcpy(clientSSID, this->location.ssid); - strcpy(clientPass, this->location.password); + if (!strlen(clientSSID)) { + log("connecting to autoupdate server"); + strcpy(clientSSID, this->current_version.ssid); + strcpy(clientPass, this->current_version.password); + } return; case WL_NO_SSID_AVAIL: @@ -125,7 +137,7 @@ class AutoUpdater { return; case WL_CONNECTED: - if (WiFi.SSID() != String(this->location.ssid)) { + if (WiFi.SSID() != String(this->current_version.ssid)) { log("disconnecting from WiFi"); WiFi.disconnect(false, true); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; @@ -136,6 +148,12 @@ class AutoUpdater { this->status = Connected; return; + case WL_IDLE_STATUS: + EVERY_N_MILLIS(300) { + Serial.print("..."); + } + break; + default: Serial.printf("OTA: wifi %d", (int)s); break; @@ -144,16 +162,18 @@ class AutoUpdater { void do_request() { log("connecting"); - if (!this->_client.connect(this->location.host, this->port)) { + if (!this->_client.connect(this->host_name.c_str(), this->port)) { // this->current_version.host abort("connect failed"); return; } // Get the contents of the bin file log("requesting update package"); - this->_client.print(String("GET ") + this->path + " HTTP/1.1\r\n" + - "Host: " + this->location.host + "\r\n" + - "Cache-Control: no-cache\r\n\r\n"); + auto request = String("GET ") + this->path + " HTTP/1.1\r\n" + + "Host: " + this->host_name + "\r\n" + + "Cache-Control: no-cache\r\n\r\n"; + Serial.println(request); + this->_client.print(request); log("awaiting response"); timeoutTimer.start(5000); diff --git a/wled00/button.cpp b/wled00/button.cpp index b251a0c4a6..564b7f1161 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -8,8 +8,10 @@ #define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms #define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press #define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 -#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP -#define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset +#define WLED_LONG_AP 3000 // how long button 0 needs to be held to activate WLED-AP + +// SteveE: geez you can't put factory reset so close to "just turn on the AP" +#define WLED_LONG_FACTORY_RESET 30000 // how long button 0 needs to be held to trigger a factory reset static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage From 9b2da8d011321377fdec6e8f6b07336cc5726bbd Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 02:21:05 -0700 Subject: [PATCH 084/263] add roles and master commands --- usermods/Tubes/Tubes.h | 35 ++++++-- usermods/Tubes/controller.h | 143 ++++++++++++++++++++++++------ usermods/Tubes/debug.h | 16 ++-- usermods/Tubes/global_state.h | 1 + usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/master.h | 162 ++++++++++++++++++++++++++++++++++ usermods/Tubes/node.h | 2 + usermods/Tubes/updater.h | 14 +-- 8 files changed, 330 insertions(+), 45 deletions(-) create mode 100644 usermods/Tubes/master.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index c5bda68df5..5211ae5eeb 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -1,14 +1,11 @@ #pragma once +#include #include "wled.h" #include "util.h" #include "options.h" -// #define MASTERCONTROL - -#define MASTER_PIN 6 - // #define USERADIO #include "FX.h" @@ -16,17 +13,23 @@ #include "beats.h" #include "virtual_strip.h" #include "led_strip.h" +#include "master.h" #include "controller.h" #include "debug.h" +#define MASTER_PIN 25 +#define LEGACY_PIN 32 // DigUno Q4 + + class TubesUsermod : public Usermod { private: BeatController beats; PatternController controller = PatternController(MAX_REAL_LEDS, &beats); DebugController debug = DebugController(&controller); - int* master = NULL; /* master.h deleted */ + Master *master = nullptr; + bool isLegacy = false; void randomize() { randomSeed(esp_random()); @@ -36,6 +39,16 @@ class TubesUsermod : public Usermod { public: void setup() { + ControllerRole role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + if (role == MasterRole) { + master = new Master(&controller); + } + + pinMode(MASTER_PIN, INPUT_PULLUP); + pinMode(LEGACY_PIN, INPUT_PULLUP); + if (digitalRead(MASTER_PIN) == LOW) { + } + isLegacy = (digitalRead(MASTER_PIN) == LOW); randomize(); // Override some behaviors on all Tubes @@ -48,7 +61,11 @@ class TubesUsermod : public Usermod { // Start timing globalTimer.setup(); beats.setup(); - controller.setup(0); + controller.setup(); + if (controller.isMaster()) { + master = new Master(&controller); + master->setup(); + } debug.setup(); } @@ -58,18 +75,22 @@ class TubesUsermod : public Usermod { randomize(); } + if (master) + master->update(); beats.update(); controller.update(); debug.update(); // Draw after everything else is done - controller.led_strip->update(master != NULL); // ~25us + controller.led_strip->update(); } void handleOverlayDraw() { // Draw effects layers over whatever WLED is doing. this->controller.handleOverlayDraw(); + // if (master) + // master->handleOverlayDraw(); this->debug.handleOverlayDraw(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index f7a37be542..933cb1dd4c 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -22,6 +22,7 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define ROLE_EEPROM_LOCATION 2559 #define IDENTIFY_STUCK_PATTERNS @@ -38,6 +39,18 @@ typedef struct { TubeState next; } TubeStates; +typedef enum ControllerRole : uint8_t { + DefaultRole = 10, // Turn on in power saving mode + InstallationRole = 20, // Disable power-saving mode + LegacyRole = 100, // 1/2 the pixels + MasterRole = 200 // Controls all the others +} ControllerRole; + +typedef struct { + char key; + uint8_t arg; +} Action; + #define NUM_VSTRIPS 3 #define DEBOUNCE_TIME 40 @@ -80,10 +93,10 @@ class PatternController : public MessageReceiver { uint8_t num_leds; VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; - bool isMaster = false; uint8_t paletteOverride = 0; uint8_t patternOverride = 0; uint16_t wled_fader = 0; + ControllerRole role; AutoUpdater auto_updater = AutoUpdater(); @@ -129,17 +142,35 @@ class PatternController : public MessageReceiver { #endif } } - - void setup(bool isMaster) + + bool isMaster() { + return this->role == MasterRole; + } + + void setup() { this->node->setup(); - this->isMaster = isMaster; - this->options.power_save = true; + this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + this->options.power_save = false; this->options.debugging = false; - if (isMaster) - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; - else - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + switch (role) { + case MasterRole: + this->node->reset(4050); // MASTER ID + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + break; + + case LegacyRole: + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + this->options.power_save = false; + break; + + default: + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + this->options.power_save = true; + break; + } this->load_options(this->options); #ifdef USELCD @@ -315,13 +346,14 @@ class PatternController : public MessageReceiver { } } - if (this->options.power_save) { + // Power Save mode: reduce number of displayed pixels + // Only affects non-powered poles + if (this->options.power_save && this->role == DefaultRole) { // Screen door effectn uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { - CRGB c = strip.getPixelColor(i); - strip.setPixelColor(i, CRGB(c.r>>3,c.g>>3,c.b>>3)); + strip.setPixelColor(i, CRGB::Black); } } } @@ -635,6 +667,14 @@ class PatternController : public MessageReceiver { this->options.power_save = power_save; this->broadcast_options(); } + + void setRole(ControllerRole role) { + this->role = role; + Serial.printf("Role = %d", role); + EEPROM.write(ROLE_EEPROM_LOCATION, role); + ESP.restart(); + } + SyncMode randomSyncMode() { uint8_t r = random8(128); @@ -752,11 +792,6 @@ class PatternController : public MessageReceiver { case '~': ESP.restart(); break; - case 'M': - // Cancel any overrides - this->paletteOverrideTimer.stop(); - this->patternOverrideTimer.stop(); - break; case '-': b = this->options.brightness; @@ -836,17 +871,22 @@ class PatternController : public MessageReceiver { this->node->reset(arg >> 4); return; + case 'V': case 'g': - for (int i=0; i< 10; i++) - addGlitter(); + case 'A': + case 'W': + case 'X': + case 'M': { + Action action = { + .key = command[0], + .arg = (uint8_t)(arg >> 8) + }; + this->broadcast_action(action); return; + } - case 'W': - // Clear wifi - Serial.print("Clearing WiFi connection."); - strcpy(clientSSID, ""); - strcpy(clientPass, ""); - WiFi.disconnect(false, true); + case 'R': + this->setRole((ControllerRole)(arg >> 8)); return; case '?': @@ -894,6 +934,13 @@ class PatternController : public MessageReceiver { this->broadcast_state(); } + void broadcast_action(Action& action) { + if (!this->node->is_following()) { + this->onAction(&action); + } + this->node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); + } + void broadcast_state() { this->node->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(TubeStates)); } @@ -904,7 +951,7 @@ class PatternController : public MessageReceiver { void broadcast_autoupdate() { AutoUpdateOffer offer; - this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.location, sizeof(this->auto_updater.location)); + this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.current_version, sizeof(this->auto_updater.current_version)); } virtual void onCommand(CommandId command, void *data) { @@ -942,9 +989,53 @@ class PatternController : public MessageReceiver { case COMMAND_UPGRADE: this->auto_updater.start((AutoUpdateOffer*)data); return; + + case COMMAND_ACTION: + this->onAction((Action*)data); + return; } Serial.printf("UNKNOWN COMMAND %02X", command); } + void onAction(Action* action) { + switch (action->key) { + case 'A': + Serial.println("Turning on WiFi access point."); + WLED::instance().initAP(true); + return; + + case 'X': + ESP.restart(); + return; + + case 'W': + Serial.println("Clearing WiFi connection."); + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + WiFi.disconnect(false, true); + return; + + case 'g': + Serial.println("glitter!"); + for (int i=0; i< 10; i++) + addGlitter(); + return; + + case 'M': + Serial.println("cancel manual mode"); + this->paletteOverrideTimer.stop(); + this->patternOverrideTimer.stop(); + break; + + case 'V': + // Version check: try to update, but leave wifi on for the script + if (this->auto_updater.current_version.version < action->arg) { + WLED::instance().initAP(true); + this->auto_updater.start(); + } + break; + } + } + }; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index a15998cd30..34aba31dfd 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -76,6 +76,10 @@ class DebugController { formatted_time(millis()).c_str() ); + if (this->controller->isMaster()) { + Serial.print("=== PRIMARY CONTROLER\n"); + } + // Dump WLED status char mode_name[50]; char palette_name[50]; @@ -101,13 +105,13 @@ class DebugController { Serial.println(); Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", - this->controller->auto_updater.location.version, + this->controller->auto_updater.current_version.version, this->controller->auto_updater.status, - this->controller->auto_updater.location.ssid, - this->controller->auto_updater.location.host[0], - this->controller->auto_updater.location.ssid[1], - this->controller->auto_updater.location.ssid[2], - this->controller->auto_updater.location.ssid[3] + this->controller->auto_updater.current_version.ssid, + this->controller->auto_updater.current_version.host[0], + this->controller->auto_updater.current_version.ssid[1], + this->controller->auto_updater.current_version.ssid[2], + this->controller->auto_updater.current_version.ssid[3] ); } diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index 0e1af1c008..e86839512e 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,5 +57,6 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_ACTION = 0x30; const static CommandId COMMAND_UPGRADE = 0xE0; const static CommandId COMMAND_RESET = 0xF0; \ No newline at end of file diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index a15ddfaf47..96830f87e8 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -33,7 +33,7 @@ class LEDs { } } - void update(bool reverse=false) { + void update() { EVERY_N_MILLISECONDS( this->TARGET_REFRESH ) { this->fps++; } diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h new file mode 100644 index 0000000000..0e7ab99a5d --- /dev/null +++ b/usermods/Tubes/master.h @@ -0,0 +1,162 @@ +#pragma once + +#include "timer.h" +#include "controller.h" +#include "led_strip.h" + +#define X_AXIS_PIN 20 +#define Y_AXIS_PIN 21 + +#define BUTTON_PIN_1 70 // SELECT? +#define BUTTON_PIN_2 71 // SKIP +#define BUTTON_PIN_3 72 // SET COLOR +#define BUTTON_PIN_4 23 // TAP +#define BUTTON_PIN_5 73 + + +class Master { + public: + uint8_t taps=0; + Timer tapTimer; + uint16_t tapTime[16]; + + Background background; + uint8_t palette_mode = false; + uint8_t palette_id = 0; + + PatternController *controller; + Button button[5]; + + Master(PatternController *controller) { + this->controller = controller; + } + + void setup() { + this->button[0].setup(BUTTON_PIN_1); + this->button[1].setup(BUTTON_PIN_2); + this->button[2].setup(BUTTON_PIN_3); + this->button[3].setup(BUTTON_PIN_4); + this->button[4].setup(BUTTON_PIN_5); + Serial.println((char *)F("Master: ok")); + } + + void update() { + for (uint8_t i=0; i < 5; i++) { + if (button[i].triggered()) { + if (button[i].pressed()) + this->onButtonPress(i); + else + this->onButtonRelease(i); + } + } + + if (this->taps && this->tapTimer.since_mark() > 10000) { + this->taps = 0; + this->fail(); + } + + this->updateStatus(this->controller, this->controller->led_strip); + } + + void ok() { + addFlash(CRGB::Green); + } + + void fail() { + addFlash(CRGB::Red); + } + + void onButtonPress(uint8_t button) { + if (button == 0) + return; + + if (button == 1) { + Serial.println((char *)F("Skip >>")); + this->controller->force_next(); + this->ok(); + return; + } + + if (button == 3) { + this->tap(); + return; + } + + Serial.print((char *)F("Pressed ")); + Serial.println(button); + } + + void onButtonRelease(uint8_t button) { +#ifdef EXTRA_STUFF + if (button == 2) { + if (this->palette_mode) + this->controller->_load_palette(this->palette_id); + this->palette_mode = false; + } +#endif + + if (button == 3) { + if (this->taps == 0) + return; + this->tap(); + return; + } + + Serial.print((char *)F("Released ")); + Serial.println(button); + } + + void tap() { + Serial.println((char *)F("tap")); + if (!this->taps) { + this->tapTimer.start(0); + } + + uint32_t time = this->tapTimer.since_mark(); + this->tapTime[this->taps++] = time; + + uint32_t bpm = 0; + if (this->taps > 4) { + // Can study this later to make BPM detection better + + bpm = 60000*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat + if (bpm < 70*256) + bpm *= 2; + else if (bpm > 140*256) + bpm /= 2; + } + + if (this->taps == 16) { + Serial.println("OK! taps"); + this->taps = 0; + this->controller->set_tapped_bpm(bpm); + this->ok(); + } + } + + void updateStatus(PatternController *controller, LEDs *strip) { + if (this->taps) { + this->displayProgress(this->taps); + } else if (this->palette_mode) { + this->displayPalette(this->background); + } else { + uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; + strip->leds[beat_pos] = CRGB::White; + } + } + + void displayProgress(uint8_t progress) { + fill_solid(this->controller->led_strip->leds, 16, CRGB::Black); + fill_solid(this->controller->led_strip->leds, progress % 16, CRGB(128,128,128)); + } + + void displayPalette(Background &background) { + for (int i = 0; i < 16; i++) { + Segment& segment = strip.getMainSegment(); + uint32_t color = CRGB(segment.color_from_palette(i * 16, false, true, 255)); + this->controller->led_strip->leds[i] = color; + } + } + +}; + diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 07bc07c415..a36876f7c2 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -58,6 +58,8 @@ const char *command_name(CommandId command) { return "OPTIONS"; case COMMAND_RESET: return "RESET"; + case COMMAND_ACTION: + return "ACTION"; default: return "?COMMAND?"; } diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index b048bc3812..b73d057b64 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 3 +#define RELEASE_VERSION 4 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { @@ -70,13 +70,13 @@ class AutoUpdater { } } - void start(AutoUpdateOffer *new_version) { + void start(AutoUpdateOffer *new_version = nullptr) { if (this->status != Idle) { log("update already in progress."); return; } - if (new_version->version <= current_version.version) { + if (new_version && new_version->version <= current_version.version) { log("don't need to update to that version."); return; } @@ -86,7 +86,9 @@ class AutoUpdater { _storedPass = String(clientPass); WLED::instance().disableWatchdog(); - memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); + if (new_version) { + memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); + } log("starting autoupdate"); this->status = Started; @@ -161,6 +163,7 @@ class AutoUpdater { } void do_request() { + WLED::instance().disableWatchdog(); log("connecting"); if (!this->_client.connect(this->host_name.c_str(), this->port)) { // this->current_version.host abort("connect failed"); @@ -238,9 +241,10 @@ class AutoUpdater { return; }; + WLED::instance().disableWatchdog(); this->progress = 0; vTaskDelay(500); - uint8_t buf[2048]; + uint8_t buf[512]; int lr; while ((lr = client.read(buf, sizeof(buf))) > 0) { size_t written = Update.write(buf, lr); From c8098387c984f1e10dc690d2139ce7dda7a8af06 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 04:09:17 -0700 Subject: [PATCH 085/263] Fix master mode and tempo tapping --- usermods/Tubes/Tubes.h | 10 ++-------- usermods/Tubes/controller.h | 10 ++++++++-- usermods/Tubes/master.h | 29 ++++++++++++++++++++++------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 5211ae5eeb..f898600b89 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -1,6 +1,5 @@ #pragma once -#include #include "wled.h" #include "util.h" @@ -39,11 +38,6 @@ class TubesUsermod : public Usermod { public: void setup() { - ControllerRole role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); - if (role == MasterRole) { - master = new Master(&controller); - } - pinMode(MASTER_PIN, INPUT_PULLUP); pinMode(LEGACY_PIN, INPUT_PULLUP); if (digitalRead(MASTER_PIN) == LOW) { @@ -89,8 +83,8 @@ class TubesUsermod : public Usermod { { // Draw effects layers over whatever WLED is doing. this->controller.handleOverlayDraw(); - // if (master) - // master->handleOverlayDraw(); + if (master) + master->handleOverlayDraw(); this->debug.handleOverlayDraw(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 933cb1dd4c..fc4fd0fd9f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1,5 +1,6 @@ #pragma once +#include #include "wled.h" #include "FX.h" #include "updater.h" @@ -13,7 +14,7 @@ #include "global_state.h" #include "node.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 100; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE @@ -150,7 +151,10 @@ class PatternController : public MessageReceiver { void setup() { this->node->setup(); + EEPROM.begin(2560); this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + EEPROM.end(); + Serial.printf("Role = %d", this->role); this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; this->options.power_save = false; @@ -671,10 +675,12 @@ class PatternController : public MessageReceiver { void setRole(ControllerRole role) { this->role = role; Serial.printf("Role = %d", role); + EEPROM.begin(2560); EEPROM.write(ROLE_EEPROM_LOCATION, role); + EEPROM.end(); + delay(10); ESP.restart(); } - SyncMode randomSyncMode() { uint8_t r = random8(128); diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 0e7ab99a5d..14fb9f8ecc 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -54,8 +54,10 @@ class Master { this->taps = 0; this->fail(); } + } - this->updateStatus(this->controller, this->controller->led_strip); + void handleOverlayDraw() { + this->updateStatus(this->controller); } void ok() { @@ -129,32 +131,45 @@ class Master { if (this->taps == 16) { Serial.println("OK! taps"); this->taps = 0; + auto frac = bpm % 256; + + // Slight snap to beat + if (frac < 128) + bpm -= frac / 2; + else if (frac > 128) + bpm += (256-frac) / 2; + this->controller->set_tapped_bpm(bpm); this->ok(); } } - void updateStatus(PatternController *controller, LEDs *strip) { + void updateStatus(PatternController *controller) { if (this->taps) { this->displayProgress(this->taps); } else if (this->palette_mode) { this->displayPalette(this->background); } else { uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; - strip->leds[beat_pos] = CRGB::White; + strip.setPixelColor(16 - beat_pos, CRGB::White); } } void displayProgress(uint8_t progress) { - fill_solid(this->controller->led_strip->leds, 16, CRGB::Black); - fill_solid(this->controller->led_strip->leds, progress % 16, CRGB(128,128,128)); + for (int i = 0; i < 16; i++) { + if (i < progress % 16) { + strip.setPixelColor(16 - i, CRGB(128,128,128)); + } else { + strip.setPixelColor(16 - i, CRGB::Black); + } + } } void displayPalette(Background &background) { for (int i = 0; i < 16; i++) { Segment& segment = strip.getMainSegment(); - uint32_t color = CRGB(segment.color_from_palette(i * 16, false, true, 255)); - this->controller->led_strip->leds[i] = color; + auto color = CRGB(segment.color_from_palette(i * 16, false, true, 255)); + strip.setPixelColor(i, color); } } From 60ee6277194bacf0383f1f7186a123dfe23eb5bb Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 16:02:37 -0700 Subject: [PATCH 086/263] New autoupdate modes --- usermods/Tubes/Tubes.h | 8 +++- usermods/Tubes/bluetooth.h | 2 +- usermods/Tubes/controller.h | 84 ++++++++++++++--------------------- usermods/Tubes/debug.h | 21 +++++---- usermods/Tubes/firmware.sh | 11 ++++- usermods/Tubes/global_state.h | 4 +- usermods/Tubes/node.h | 44 +++++++++++------- usermods/Tubes/updater.h | 82 +++++++++++++++++++++++++++++----- 8 files changed, 164 insertions(+), 92 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index f898600b89..57848a7a27 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -83,8 +83,14 @@ class TubesUsermod : public Usermod { { // Draw effects layers over whatever WLED is doing. this->controller.handleOverlayDraw(); + this->debug.handleOverlayDraw(); if (master) master->handleOverlayDraw(); - this->debug.handleOverlayDraw(); + + // When AP mode is on, make sure it's obvious + if (apActive) { + strip.setPixelColor(0, CRGB::Purple); + strip.setPixelColor(1, CRGB::Black); + } } }; diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 0322264939..d98f5bcf88 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -448,7 +448,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { // Process the command this->receiver->onCommand( storage.header.id, - COMMAND_UPDATE, + COMMAND_STATE, &storage.current ); this->receiver->onCommand( diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index fc4fd0fd9f..9a5f5cdb7d 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -99,7 +99,7 @@ class PatternController : public MessageReceiver { uint16_t wled_fader = 0; ControllerRole role; - AutoUpdater auto_updater = AutoUpdater(); + AutoUpdater updater = AutoUpdater(); Timer graphicsTimer; Timer updateTimer; @@ -311,7 +311,7 @@ class PatternController : public MessageReceiver { } } - this->auto_updater.update(); + this->updater.update(); #ifdef USELCD if (this->lcd->active) { @@ -368,42 +368,7 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } - drawOTAOverlay(); - - // When AP mode is on, make sure it's obvious - if (apActive) { - strip.setPixelColor(0, CRGB::Purple); - strip.setPixelColor(1, CRGB::Black); - } - } - - void drawOTAOverlay() { - CRGB c; - switch (this->auto_updater.status) { - case Started: - case Connected: - case Received: - c = CRGB::Yellow; - if (millis() % 1000 < 500) { - c = CRGB::Black; - } - break; - - case Failed: - c = CRGB::Red; - break; - - case Complete: - c = CRGB::Green; - break; - - case Idle: - default: - return; - } - for (int i = 0; i < 20; i++) { - strip.setPixelColor(i, c); - } + this->updater.handleOverlayDraw(); } void restart_phrase() { @@ -903,7 +868,6 @@ class PatternController : public MessageReceiver { Serial.println(F("m### - sync mode")); Serial.println(F("c### - colors")); Serial.println(F("e### - effects")); - Serial.println("w### - wled pattern"); Serial.println(); Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); @@ -911,10 +875,16 @@ class PatternController : public MessageReceiver { Serial.println("@ - power save mode"); Serial.println("U - begin auto-update"); Serial.println("O - offer an auto-update"); + Serial.println("==== global actions ===="); + Serial.println("A - turn on access point"); + Serial.println("W - forget WiFi client"); + Serial.println("X - restart"); + Serial.println("V### - auto-upgrade to version"); + Serial.println("M - cancel manual pattern override"); return; case 'U': - this->auto_updater.start(); + this->updater.start(); return; case 'O': @@ -947,8 +917,12 @@ class PatternController : public MessageReceiver { this->node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); } + void broadcast_info(NodeInfo *info) { + this->node->sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); + } + void broadcast_state() { - this->node->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(TubeStates)); + this->node->sendCommand(COMMAND_STATE, &this->current_state, sizeof(TubeStates)); } void broadcast_options() { @@ -956,14 +930,15 @@ class PatternController : public MessageReceiver { } void broadcast_autoupdate() { - AutoUpdateOffer offer; - this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.current_version, sizeof(this->auto_updater.current_version)); + this->node->sendCommand(COMMAND_UPGRADE, &this->updater.current_version, sizeof(this->updater.current_version)); } virtual void onCommand(CommandId command, void *data) { switch (command) { - case COMMAND_RESET: - // TODO + case COMMAND_INFO: + Serial.printf(" \"%s\"\n", + ((NodeInfo*)data)->message + ); return; case COMMAND_OPTIONS: @@ -975,7 +950,7 @@ class PatternController : public MessageReceiver { ); return; - case COMMAND_UPDATE: { + case COMMAND_STATE: { auto update_data = (TubeStates*)data; TubeState state; @@ -993,7 +968,7 @@ class PatternController : public MessageReceiver { } case COMMAND_UPGRADE: - this->auto_updater.start((AutoUpdateOffer*)data); + this->updater.start((AutoUpdateOffer*)data); return; case COMMAND_ACTION: @@ -1035,12 +1010,19 @@ class PatternController : public MessageReceiver { break; case 'V': - // Version check: try to update, but leave wifi on for the script - if (this->auto_updater.current_version.version < action->arg) { - WLED::instance().initAP(true); - this->auto_updater.start(); + // Version check: prepare for update + if (this->updater.current_version.version >= action->arg) + break; + + this->updater.ready(); + break; + + case 'U': + if (this->updater.status == Ready) { + this->updater.start(); } break; + } } diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 34aba31dfd..c05977ae4d 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -76,9 +76,12 @@ class DebugController { formatted_time(millis()).c_str() ); + Serial.printf("=== Controller: "); if (this->controller->isMaster()) { - Serial.print("=== PRIMARY CONTROLER\n"); + Serial.print("PRIMARY "); } + Serial.printf("role=%d\n", + this->controller->role); // Dump WLED status char mode_name[50]; @@ -104,14 +107,14 @@ class DebugController { } Serial.println(); - Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", - this->controller->auto_updater.current_version.version, - this->controller->auto_updater.status, - this->controller->auto_updater.current_version.ssid, - this->controller->auto_updater.current_version.host[0], - this->controller->auto_updater.current_version.ssid[1], - this->controller->auto_updater.current_version.ssid[2], - this->controller->auto_updater.current_version.ssid[3] + Serial.printf("=== firmware: v%d from SSID %s %u.%u.%u.%u OTA=%d\n\n", + this->controller->updater.current_version.version, + this->controller->updater.current_version.ssid, + this->controller->updater.current_version.host[0], + this->controller->updater.current_version.ssid[1], + this->controller->updater.current_version.ssid[2], + this->controller->updater.current_version.ssid[3], + this->controller->updater.status ); } diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 38e7d26d4a..5d204756ee 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -9,7 +9,7 @@ ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools upload_firmware() { echo "Uploading firmware" sftp control@brcac.com <header.uplinkId, command_name(message->command) ); - if (message->recipients == ROOT) + if (message->recipients == RECIPIENTS_ROOT) Serial.printf(":ROOT"); if (rssi) Serial.printf(" %ddB ", rssi); @@ -217,16 +223,20 @@ class LightNode { bool ignore = false; switch (message->recipients) { - case ALL: + case RECIPIENTS_ALL: // Ignore this message if not from the uplink ignore = (message->header.id != this->header.uplinkId); break; - case ROOT: - // Ignore this message if not from a downlink + case RECIPIENTS_ROOT: + // Ignore this message if not from one of this node's downlinks ignore = (message->header.uplinkId != this->header.id); break; + case RECIPIENTS_INFO: + ignore = false; + break; + default: // ignore this! ignore = true; @@ -243,7 +253,7 @@ class LightNode { } // Execute the received command - if (message->recipients != ROOT || !this->is_following()) { + if (message->recipients != RECIPIENTS_ROOT || !this->is_following()) { Serial.print(" >> "); print_message(message, rssi); Serial.print(" "); @@ -264,10 +274,10 @@ class LightNode { } // Re-broadcast the message if appropriate - if (!this->rebroadcastTimer.ended()) { + if (!this->rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { message->header = this->header; if (!this->is_following()) - message->recipients = ALL; + message->recipients = RECIPIENTS_ALL; this->broadcast(message, true); } } @@ -301,11 +311,15 @@ class LightNode { NodeMessage message; message.header = header; - if (command != COMMAND_UPDATE && this->is_following()) { + if (command == COMMAND_INFO) { + message.recipients = RECIPIENTS_INFO; + } else if (command == COMMAND_STATE) { + message.recipients = RECIPIENTS_ALL; + } else if (this->is_following()) { // Follower nodes must request that the root re-sends this message - message.recipients = ROOT; + message.recipients = RECIPIENTS_ROOT; } else { - message.recipients = ALL; + message.recipients = RECIPIENTS_ALL; } message.command = command; memcpy(&message.data, data, len); diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index b73d057b64..2d0519fd5e 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -15,9 +15,10 @@ String getHeaderValue(String header, String headerName) { typedef enum UpdateWorkflowStatus: uint8_t { Idle=0, - Started=1, - Connected=2, - Received=4, + Ready=10, + Started=50, + Connected=60, + Received=80, Complete=100, Failed=101, } UpdateWorkflowStatus; @@ -42,20 +43,31 @@ class AutoUpdater { String _storedSSID = ""; String _storedPass = ""; + String _storedAPSSID = ""; + String _storedAPPass = ""; int progress = 0; WiFiClient _client; UpdateWorkflowStatus status = Idle; Timer timeoutTimer; - Timer progressTimer; + Timer displayStatusTimer; void update() { switch (this->status) { case Complete: + if (this->displayStatusTimer.ended()) { + doReboot = true; + this->status = Idle; + } + return; + case Failed: - if (this->progressTimer.ended()) + if (this->displayStatusTimer.ended()) this->status = Idle; + return; + + case Ready: case Idle: case Received: return; @@ -84,7 +96,9 @@ class AutoUpdater { // The auto-update process might break the current connection _storedSSID = String(clientSSID); _storedPass = String(clientPass); - WLED::instance().disableWatchdog(); + _storedAPSSID = String(apSSID); + _storedAPPass = String(apPass); + otaLock = false; if (new_version) { memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); @@ -92,17 +106,61 @@ class AutoUpdater { log("starting autoupdate"); this->status = Started; + this->displayStatusTimer.stop(); + } + + void ready() { + log("ready for update - turning on updater AP"); + strcpy(apSSID, "WLED-UPDATE"); + strcpy(apPass, "update1234"); + WLED::instance().initAP(true); + this->status = Ready; } void stop() { this->_client.stop(); strcpy(clientSSID, _storedSSID.c_str()); strcpy(clientPass, _storedPass.c_str()); + strcpy(apSSID, _storedAPSSID.c_str()); + strcpy(apPass, _storedAPPass.c_str()); WiFi.disconnect(false, true); WLED::instance().enableWatchdog(); this->status = Idle; } + void handleOverlayDraw() { + CRGB c; + switch (this->status) { + case Ready: + c = CRGB::Purple; + break; + + case Started: + case Connected: + case Received: + c = CRGB::Yellow; + if (millis() % 1000 < 500) { + c = CRGB::Black; + } + break; + + case Failed: + c = CRGB::Red; + break; + + case Complete: + c = CRGB::Green; + break; + + case Idle: + default: + return; + } + for (int i = 0; i < 20; i++) { + strip.setPixelColor(i, c); + } + } + private: void log(const char *message) { Serial.printf("OTA: %s\n", message); @@ -114,9 +172,9 @@ class AutoUpdater { void abort(const char *message) { log(message); - this->status = Failed; - this->progressTimer.start(10000); this->stop(); + this->status = Failed; + this->displayStatusTimer.start(30000); } void do_connect() { @@ -244,7 +302,7 @@ class AutoUpdater { WLED::instance().disableWatchdog(); this->progress = 0; vTaskDelay(500); - uint8_t buf[512]; + uint8_t buf[4096]; int lr; while ((lr = client.read(buf, sizeof(buf))) > 0) { size_t written = Update.write(buf, lr); @@ -270,10 +328,10 @@ class AutoUpdater { return; } - doReboot = true; log("update successfully completed. Rebooting."); - this->status = Complete; - this->progressTimer.start(10000); this->stop(); + this->status = Complete; + this->displayStatusTimer.start(10000); } + }; From acfbe0dd998e429755b211807d8d8037937b8078 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 16:58:08 -0700 Subject: [PATCH 087/263] Better power save mode with button override --- usermods/Tubes/Tubes.h | 11 ++++++++ usermods/Tubes/controller.h | 54 +++++++++++++++++++++++++------------ usermods/Tubes/debug.h | 6 +++-- wled00/button.cpp | 11 ++++++-- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 57848a7a27..48f7e6027d 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -93,4 +93,15 @@ class TubesUsermod : public Usermod { strip.setPixelColor(1, CRGB::Black); } } + + bool handleButton(uint8_t b) { + // Special code for handling the "power save" button + if (b == 100) { + this->controller.togglePowerSave(); + return true; + } + + return false; + } + }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 9a5f5cdb7d..a926d609a5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -29,7 +29,6 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; typedef struct { bool debugging; - bool power_save; uint8_t brightness; uint8_t reserved[12]; @@ -41,9 +40,11 @@ typedef struct { } TubeStates; typedef enum ControllerRole : uint8_t { + UnknownRole = 0, DefaultRole = 10, // Turn on in power saving mode - InstallationRole = 20, // Disable power-saving mode - LegacyRole = 100, // 1/2 the pixels + CampRole = 50, // Turn on in non-power-saving mode + InstallationRole = 100, // Disable power-saving mode completely + LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary MasterRole = 200 // Controls all the others } ControllerRole; @@ -98,6 +99,7 @@ class PatternController : public MessageReceiver { uint8_t patternOverride = 0; uint16_t wled_fader = 0; ControllerRole role; + bool power_save = true; // Power save ALWAYS starts on. Some roles just ignore it AutoUpdater updater = AutoUpdater(); @@ -153,11 +155,14 @@ class PatternController : public MessageReceiver { this->node->setup(); EEPROM.begin(2560); this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + if (this->role == 255) { + this->role = DefaultRole; + } EEPROM.end(); Serial.printf("Role = %d", this->role); + this->power_save = (this->role < CampRole); this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.power_save = false; this->options.debugging = false; switch (role) { case MasterRole: @@ -167,12 +172,10 @@ class PatternController : public MessageReceiver { case LegacyRole: this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.power_save = false; break; default: this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.power_save = true; break; } this->load_options(this->options); @@ -352,8 +355,8 @@ class PatternController : public MessageReceiver { // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles - if (this->options.power_save && this->role == DefaultRole) { - // Screen door effectn + if (this->power_save && this->role < InstallationRole) { + // Screen door effect to save power uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { @@ -630,11 +633,13 @@ class PatternController : public MessageReceiver { this->broadcast_options(); } + void togglePowerSave() { + setPowerSave(!this->power_save); + } + void setPowerSave(bool power_save) { Serial.printf("power_save: %d\n", power_save); - - this->options.power_save = power_save; - this->broadcast_options(); + this->power_save = power_save; } void setRole(ControllerRole role) { @@ -757,13 +762,13 @@ class PatternController : public MessageReceiver { case 'd': this->setDebugging(!this->options.debugging); break; - case '@': - this->setPowerSave(!this->options.power_save); - break; case '~': ESP.restart(); break; - + case '@': + this->togglePowerSave(); + break; + case '-': b = this->options.brightness; while (*command++ == '-') @@ -843,7 +848,7 @@ class PatternController : public MessageReceiver { return; case 'V': - case 'g': + case 'G': case 'A': case 'W': case 'X': @@ -856,6 +861,16 @@ class PatternController : public MessageReceiver { return; } + case 'P': { + // Toggle power save + Action action = { + .key = command[0], + .arg = !this->power_save, + }; + this->broadcast_action(action); + break; + } + case 'R': this->setRole((ControllerRole)(arg >> 8)); return; @@ -872,7 +887,7 @@ class PatternController : public MessageReceiver { Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); - Serial.println("@ - power save mode"); + Serial.println("@ - toggle power saving mode"); Serial.println("U - begin auto-update"); Serial.println("O - offer an auto-update"); Serial.println("==== global actions ===="); @@ -990,6 +1005,11 @@ class PatternController : public MessageReceiver { ESP.restart(); return; + case '@': + Serial.print("Setting power save to %d\n"); + this->setPowerSave(action->arg); + return; + case 'W': Serial.println("Clearing WiFi connection."); strcpy(clientSSID, ""); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c05977ae4d..efb1d7876b 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -80,8 +80,10 @@ class DebugController { if (this->controller->isMaster()) { Serial.print("PRIMARY "); } - Serial.printf("role=%d\n", - this->controller->role); + Serial.printf("role=%d power_save=%d\n", + this->controller->role, + this->controller->power_save + ); // Dump WLED status char mode_name[50]; diff --git a/wled00/button.cpp b/wled00/button.cpp index 564b7f1161..1beb0d4359 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -8,8 +8,8 @@ #define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms #define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press #define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 -#define WLED_LONG_AP 3000 // how long button 0 needs to be held to activate WLED-AP - +#define WLED_LONG_AP 2000 // how long button 0 needs to be held to activate WLED-AP +#define WLED_LONG_POWER_SAVE 8000 // how long button 0 needs to be held to DEactivate power saving mode (toggle it) // SteveE: geez you can't put factory reset so close to "just turn on the AP" #define WLED_LONG_FACTORY_RESET 30000 // how long button 0 needs to be held to trigger a factory reset @@ -268,15 +268,22 @@ void handleButton() buttonWaitTime[b] = 0; if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released) + Serial.printf("A %ld", dur); if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds + Serial.printf(" B"); WLED_FS.format(); #ifdef WLED_ADD_EEPROM_SUPPORT clearEEPROM(); #endif doReboot = true; + } else if (dur > WLED_LONG_POWER_SAVE) { + Serial.printf(" C"); + usermods.handleButton(100); } else { + Serial.printf(" D"); WLED::instance().initAP(true); } + Serial.println(); } else if (!buttonLongPressed[b]) { //short press //NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set From a7d5c69ff876191f8b446786363f4c1258d3d3a6 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 02:14:17 -0700 Subject: [PATCH 088/263] Fix energy-related bugs --- usermods/Tubes/controller.h | 68 +++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a926d609a5..982fe14fed 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -20,8 +20,8 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define STATUS_UPDATE_PERIOD 2000 -#define MIN_COLOR_CHANGE_PHRASES 2 // 4 -#define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define MIN_COLOR_CHANGE_PHRASES 4 +#define MAX_COLOR_CHANGE_PHRASES 20 #define ROLE_EEPROM_LOCATION 2559 @@ -268,7 +268,8 @@ class PatternController : public MessageReceiver { this->patternOverrideTimer.stop(); transitionDelay = 8000; // Back to long transitions - this->set_wled_pattern(auto_mode, 128, 128); + uint8_t param = modeParameter(auto_mode); + this->set_wled_pattern(auto_mode, param, param); } } @@ -396,9 +397,9 @@ class PatternController : public MessageReceiver { void update_beat() { this->current_state.bpm = this->next_state.bpm = this->beats->bpm; this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) - if (this->current_state.bpm >= 125>>8) + if (this->current_state.bpm>>8 >= 125) this->energy = HighEnergy; - else if (this->current_state.bpm > 120>>8) + else if (this->current_state.bpm>>8 > 120) this->energy = MediumEnergy; else this->energy = Chill; @@ -452,6 +453,23 @@ class PatternController : public MessageReceiver { return this->current_state.pattern_id >= numInternalPatterns; } + uint8_t modeParameter(uint8_t mode) { + switch (this->energy) { + case Boring: + // Spice things up a bit + return 128; + + case Chill: + return 90; + + case MediumEnergy: + return 120; + + case HighEnergy: + return 140; + } + } + // For now, can't crossfade between internal and WLED patterns // If currently running an WLED pattern, only select from internal patterns. uint8_t get_valid_next_pattern() { @@ -473,7 +491,7 @@ class PatternController : public MessageReceiver { for (int i = 0; i < 10; i++) { pattern_id = get_valid_next_pattern(); def = gPatterns[pattern_id]; - if (def.control.energy < this->energy) + if (def.control.energy <= this->energy) break; } #ifdef IDENTIFY_STUCK_PATTERNS @@ -507,7 +525,11 @@ class PatternController : public MessageReceiver { uint16_t set_next_palette(uint16_t phrase) { // Don't select the built-in palettes this->next_state.palette_id = random8(6, gGradientPaletteCount); - return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); + auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); + if (this->isBoring) { + phrases /= 2; + } + return phrases; } void load_effect(TubeState &tube_state) { @@ -536,20 +558,18 @@ class PatternController : public MessageReceiver { // Pick a random effect to add; boring patterns get better chance at having an effect. EffectDef def = gEffects[effect_num]; - Energy maxEnergy = this->energy; - if (this->isBoring) - maxEnergy = (Energy)((uint8_t)maxEnergy + 10); - if (def.control.energy > maxEnergy) + if (def.control.energy > this->energy) { def = gEffects[0]; + } this->next_state.effect_params = def.params; switch (def.control.duration) { - case ExtraShortDuration: return 2; - case ShortDuration: return 3; - case MediumDuration: return 6; - case LongDuration: return 10; - case ExtraLongDuration: return 20; + case ExtraShortDuration: return random(2,3); + case ShortDuration: return random(2,4); + case MediumDuration: return random(4,8); + case LongDuration: return random(6, 14); + case ExtraLongDuration: return random(10,20); } return 1; } @@ -571,7 +591,8 @@ class PatternController : public MessageReceiver { this->vstrips[this->next_vstrip]->load(background); this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; - set_wled_pattern(background.wled_fx_id, 128, 128); + uint8_t param = modeParameter(background.wled_fx_id); + set_wled_pattern(background.wled_fx_id, param, param); set_wled_palette(background.palette_id); } @@ -649,7 +670,7 @@ class PatternController : public MessageReceiver { EEPROM.write(ROLE_EEPROM_LOCATION, role); EEPROM.end(); delay(10); - ESP.restart(); + doReboot = true; } SyncMode randomSyncMode() { @@ -763,7 +784,7 @@ class PatternController : public MessageReceiver { this->setDebugging(!this->options.debugging); break; case '~': - ESP.restart(); + doReboot = true; break; case '@': this->togglePowerSave(); @@ -852,6 +873,7 @@ class PatternController : public MessageReceiver { case 'A': case 'W': case 'X': + case 'R': case 'M': { Action action = { .key = command[0], @@ -871,7 +893,7 @@ class PatternController : public MessageReceiver { break; } - case 'R': + case 'r': this->setRole((ControllerRole)(arg >> 8)); return; @@ -1002,7 +1024,11 @@ class PatternController : public MessageReceiver { return; case 'X': - ESP.restart(); + doReboot = true; + return; + + case 'R': + setRole((ControllerRole)(action->arg)); return; case '@': From a13b65182f34f25ae705b99dc83c8d6d5f1ffc73 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 02:29:43 -0700 Subject: [PATCH 089/263] ignore compiler files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bb02e36ef2..789de0a9e7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ node_modules wled-update.sh esp01-update.sh /wled00/LittleFS -replace_fs.py \ No newline at end of file +replace_fs.py +wled00/wled00.ino.cpp From 087ba0606e1f533d4903394547686b8188fbe215 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 03:55:35 -0700 Subject: [PATCH 090/263] More on-tube indicators --- usermods/Tubes/Tubes.h | 4 ++++ usermods/Tubes/controller.h | 45 +++++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 48f7e6027d..a1a780d15e 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -88,8 +88,12 @@ class TubesUsermod : public Usermod { master->handleOverlayDraw(); // When AP mode is on, make sure it's obvious + // Blink when there's a connected client if (apActive) { strip.setPixelColor(0, CRGB::Purple); + if (millis() % 10000 > 1000 && WiFi.softAPgetStationNum()) { + strip.setPixelColor(0, CRGB::Black); + } strip.setPixelColor(1, CRGB::Black); } } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 982fe14fed..c01b8ae81b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -100,6 +100,7 @@ class PatternController : public MessageReceiver { uint16_t wled_fader = 0; ControllerRole role; bool power_save = true; // Power save ALWAYS starts on. Some roles just ignore it + uint8_t flashColor = 0; AutoUpdater updater = AutoUpdater(); @@ -107,6 +108,7 @@ class PatternController : public MessageReceiver { Timer updateTimer; Timer paletteOverrideTimer; Timer patternOverrideTimer; + Timer flashTimer; #ifdef USELCD Lcd *lcd; @@ -156,7 +158,7 @@ class PatternController : public MessageReceiver { EEPROM.begin(2560); this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); if (this->role == 255) { - this->role = DefaultRole; + this->role = UnknownRole; } EEPROM.end(); Serial.printf("Role = %d", this->role); @@ -286,7 +288,7 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); // Detect manual overrides & update the current state to match. - if (this->paletteOverride && this->paletteOverrideTimer.ended()) { + if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { this->set_palette_override(0); } else if (segment.palette != this->current_state.palette_id) { this->set_palette_override(segment.palette); @@ -295,7 +297,7 @@ class PatternController : public MessageReceiver { uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; if (wled_mode < 10) wled_mode = DEFAULT_WLED_FX; - if (this->patternOverride && this->patternOverrideTimer.ended()) { + if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { this->set_pattern_override(0, wled_mode); } else if (segment.mode != wled_mode) { this->set_pattern_override(segment.mode, wled_mode); @@ -336,11 +338,12 @@ class PatternController : public MessageReceiver { this->wled_fader = 0xFFFF; } + uint16_t length = strip.getLengthTotal(); + // Crossfade between the custom pattern engine and WLED uint8_t fader = this->wled_fader >> 8; if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer - uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { CRGB c = this->led_strip->getPixelColor(i); if (fader > 0) { @@ -358,7 +361,6 @@ class PatternController : public MessageReceiver { // Only affects non-powered poles if (this->power_save && this->role < InstallationRole) { // Screen door effect to save power - uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { strip.setPixelColor(i, CRGB::Black); @@ -372,6 +374,19 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } + if (this->flashColor) { + if (flashTimer.ended()) + this->flashColor = 0; + else { + if (millis() % 4000 < 2000) { + auto chsv = CHSV(this->flashColor, 255, 255); + for (int i = 0; i < length; i++) { + strip.setPixelColor(i, CRGB(chsv)); + } + } + } + } + this->updater.handleOverlayDraw(); } @@ -462,11 +477,12 @@ class PatternController : public MessageReceiver { case Chill: return 90; - case MediumEnergy: - return 120; - case HighEnergy: return 140; + + default: + case MediumEnergy: + return 128; } } @@ -596,6 +612,10 @@ class PatternController : public MessageReceiver { set_wled_palette(background.palette_id); } + bool isUnderWledControl() { + return this->paletteOverride || this->patternOverride; + } + void set_wled_palette(uint8_t palette_id) { if (this->paletteOverride) palette_id = this->paletteOverride; @@ -873,6 +893,7 @@ class PatternController : public MessageReceiver { case 'A': case 'W': case 'X': + case 'F': case 'R': case 'M': { Action action = { @@ -1043,12 +1064,18 @@ class PatternController : public MessageReceiver { WiFi.disconnect(false, true); return; - case 'g': + case 'G': Serial.println("glitter!"); for (int i=0; i< 10; i++) addGlitter(); return; + case 'F': + Serial.println("flash!"); + this->flashTimer.start(20000); + this->flashColor = action->arg; + return; + case 'M': Serial.println("cancel manual mode"); this->paletteOverrideTimer.stop(); From 723cac46156d633d5fc625638317e38c9bbfbd84 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 05:08:48 -0700 Subject: [PATCH 091/263] Invert power save button; smooth transition fades --- usermods/Tubes/Tubes.h | 4 ++++ usermods/Tubes/controller.h | 24 ++++++++++++++++++++---- usermods/Tubes/updater.h | 2 +- wled00/button.cpp | 18 +++++++----------- wled00/wled.h | 1 + 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index a1a780d15e..fa60f0356a 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -104,6 +104,10 @@ class TubesUsermod : public Usermod { this->controller.togglePowerSave(); return true; } + if (b == 101) { + this->controller.cancelOverrides(); + return true; + } return false; } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c01b8ae81b..8287a05e19 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -44,7 +44,7 @@ typedef enum ControllerRole : uint8_t { DefaultRole = 10, // Turn on in power saving mode CampRole = 50, // Turn on in non-power-saving mode InstallationRole = 100, // Disable power-saving mode completely - LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary + LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary MasterRole = 200 // Controls all the others } ControllerRole; @@ -239,6 +239,12 @@ class PatternController : public MessageReceiver { } } + void cancelOverrides() { + // Release the WLED overrides and take over control of the strip again. + this->paletteOverrideTimer.stop(); + this->patternOverrideTimer.stop(); + } + void set_palette_override(uint8_t value) { if (value == this->paletteOverride) return; @@ -336,6 +342,16 @@ class PatternController : public MessageReceiver { // In manual mode WLED is always active if (this->patternOverride) { this->wled_fader = 0xFFFF; + transition_mode_point = 0; + } else if (wled_fader == 0xFFFF) { + // When fading down... + // Wait until the transition has completely changed + // before switching to new mode + transition_mode_point = 0xFFFFU; + } else if (wled_fader == 0) { + // When fading up... + // Transition to new mode immediately + transition_mode_point = 0; } uint16_t length = strip.getLengthTotal(); @@ -888,6 +904,7 @@ class PatternController : public MessageReceiver { this->node->reset(arg >> 4); return; + case 'U': case 'V': case 'G': case 'A': @@ -941,7 +958,7 @@ class PatternController : public MessageReceiver { Serial.println("M - cancel manual pattern override"); return; - case 'U': + case 'u': this->updater.start(); return; @@ -1078,8 +1095,7 @@ class PatternController : public MessageReceiver { case 'M': Serial.println("cancel manual mode"); - this->paletteOverrideTimer.stop(); - this->patternOverrideTimer.stop(); + this->cancelOverrides(); break; case 'V': diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 2d0519fd5e..c2f727c578 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 4 +#define RELEASE_VERSION 5 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { diff --git a/wled00/button.cpp b/wled00/button.cpp index 1beb0d4359..147a3c773a 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -8,8 +8,8 @@ #define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms #define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press #define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 -#define WLED_LONG_AP 2000 // how long button 0 needs to be held to activate WLED-AP -#define WLED_LONG_POWER_SAVE 8000 // how long button 0 needs to be held to DEactivate power saving mode (toggle it) +#define WLED_LONG_POWER_SAVE 1000 // how long button 0 needs to be held to DEactivate power saving mode (toggle it) +#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP // SteveE: geez you can't put factory reset so close to "just turn on the AP" #define WLED_LONG_FACTORY_RESET 30000 // how long button 0 needs to be held to trigger a factory reset @@ -19,7 +19,7 @@ void shortPressAction(uint8_t b) { if (!macroButton[b]) { switch (b) { - case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break; + case 0: toggleOnOff(); usermods.handleButton(101); stateUpdated(CALL_MODE_BUTTON); break; case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break; } } else { @@ -267,21 +267,17 @@ void handleButton() bool doublePress = buttonWaitTime[b]; //did we have a short press before? buttonWaitTime[b] = 0; - if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released) - Serial.printf("A %ld", dur); + if (b == 0 && dur > WLED_LONG_POWER_SAVE) { // long press on button 0 (when released) if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds - Serial.printf(" B"); WLED_FS.format(); #ifdef WLED_ADD_EEPROM_SUPPORT clearEEPROM(); #endif doReboot = true; - } else if (dur > WLED_LONG_POWER_SAVE) { - Serial.printf(" C"); - usermods.handleButton(100); - } else { - Serial.printf(" D"); + } else if (dur > WLED_LONG_AP) { WLED::instance().initAP(true); + } else { + usermods.handleButton(100); } Serial.println(); } else if (!buttonLongPressed[b]) { //short press diff --git a/wled00/wled.h b/wled00/wled.h index 055d65d3ed..b1ec79b9c6 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -463,6 +463,7 @@ WLED_GLOBAL uint16_t transitionDelayTemp _INIT(transitionDelay); // actu WLED_GLOBAL unsigned long transitionStartTime; WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") +WLED_GLOBAL uint16_t transition_mode_point _INIT(0); // point at which a transition changes mode // nightlight WLED_GLOBAL bool nightlightActive _INIT(false); From 13d21e75951c7659d1f16c39621c36c22093fe0b Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 15:12:27 -0700 Subject: [PATCH 092/263] Ability to set BPM from any node, using double click --- usermods/Tubes/Tubes.h | 5 ++++ usermods/Tubes/controller.h | 43 +++++++++++++++++++++++++++-------- usermods/Tubes/global_state.h | 1 + usermods/Tubes/node.h | 10 ++++++-- wled00/button.cpp | 6 +++-- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index fa60f0356a..7930e6b80c 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -108,6 +108,11 @@ class TubesUsermod : public Usermod { this->controller.cancelOverrides(); return true; } + if (b == 102) { + this->controller.acknowledge(); + this->controller.request_new_bpm(); + return true; + } return false; } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8287a05e19..a94dd42aa8 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -425,6 +425,19 @@ class PatternController : public MessageReceiver { this->send_update(); } + void request_new_bpm(accum88 new_bpm = 0) { + // 0 = toggle 120 to 125 + if (new_bpm == 0) + new_bpm = this->current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; + + if (this->node->is_following()) { + // Send a request up to ROOT + this->broadcast_bpm(new_bpm); + } else { + this->set_tapped_bpm(new_bpm, 0); + } + } + void update_beat() { this->current_state.bpm = this->next_state.bpm = this->beats->bpm; this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) @@ -851,9 +864,7 @@ class PatternController : public MessageReceiver { Serial.println(F("nope")); return; } - this->beats->set_bpm(arg); - this->update_beat(); - this->send_update(); + request_new_bpm(arg); return; case 's': @@ -1008,13 +1019,18 @@ class PatternController : public MessageReceiver { this->node->sendCommand(COMMAND_UPGRADE, &this->updater.current_version, sizeof(this->updater.current_version)); } - virtual void onCommand(CommandId command, void *data) { + void broadcast_bpm(accum88 bpm) { + // Hacked in feature: request a new BPM + this->node->sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); + } + + virtual bool onCommand(CommandId command, void *data) { switch (command) { case COMMAND_INFO: Serial.printf(" \"%s\"\n", ((NodeInfo*)data)->message ); - return; + return true; case COMMAND_OPTIONS: memcpy(&this->options, data, sizeof(this->options)); @@ -1023,7 +1039,7 @@ class PatternController : public MessageReceiver { this->options.debugging, this->options.brightness ); - return; + return true; case COMMAND_STATE: { auto update_data = (TubeStates*)data; @@ -1039,19 +1055,28 @@ class PatternController : public MessageReceiver { this->load_palette(state); this->load_effect(state); this->beats->sync(state.bpm, state.beat_frame); - return; + return true; } case COMMAND_UPGRADE: this->updater.start((AutoUpdateOffer*)data); - return; + return true; case COMMAND_ACTION: this->onAction((Action*)data); - return; + return true; + + case COMMAND_BEATS: + // the master control ignores this request, it has its own + // beat measuring. + if (this->isMaster()) + return false; + this->set_tapped_bpm(*(accum88*)data, 0); + return true; } Serial.printf("UNKNOWN COMMAND %02X", command); + return false; } void onAction(Action* action) { diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index e4f8932a91..ba1b61d4bd 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -59,4 +59,5 @@ const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_STATE = 0x20; const static CommandId COMMAND_ACTION = 0x30; const static CommandId COMMAND_INFO = 0x40; +const static CommandId COMMAND_BEATS = 0x50; const static CommandId COMMAND_UPGRADE = 0xE0; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index db9c652178..f40b2e8230 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -66,6 +66,8 @@ const char *command_name(CommandId command) { return "ACTION"; case COMMAND_INFO: return "INFO"; + case COMMAND_BEATS: + return "BEATS"; default: return "?COMMAND?"; } @@ -73,8 +75,9 @@ const char *command_name(CommandId command) { class MessageReceiver { public: - virtual void onCommand(CommandId command, void *data) { + virtual bool onCommand(CommandId command, void *data) { // Abstract: subclasses must define + return false; } }; @@ -266,11 +269,14 @@ class LightNode { strip.timebase = new_timebase; // Execute the command - this->receiver->onCommand( + auto valid = this->receiver->onCommand( message->command, &message->data ); Serial.println(); + + if (!valid) + return; } // Re-broadcast the message if appropriate diff --git a/wled00/button.cpp b/wled00/button.cpp index 147a3c773a..274aec6312 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -279,10 +279,12 @@ void handleButton() } else { usermods.handleButton(100); } - Serial.println(); } else if (!buttonLongPressed[b]) { //short press + if (b == 0 && doublePress) { + usermods.handleButton(102); + } //NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling - if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set + if (b > 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set shortPressAction(b); } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0) if (doublePress) { From b48ecb18eb4aa745f456f6b1af63ef0693002139 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 25 Aug 2022 03:47:16 -0700 Subject: [PATCH 093/263] better master control; small art role --- usermods/Tubes/controller.h | 17 ++++++++++++++++- usermods/Tubes/master.h | 22 +++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a94dd42aa8..404f19c409 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -44,7 +44,8 @@ typedef enum ControllerRole : uint8_t { DefaultRole = 10, // Turn on in power saving mode CampRole = 50, // Turn on in non-power-saving mode InstallationRole = 100, // Disable power-saving mode completely - LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary + SmallArtRole = 120, // < 1/2 the pixels, scale the art + LegacyRole = 190, // LEGACY: 1/2 the pixels, no "power saving" necessary, no scaling MasterRole = 200 // Controls all the others } ControllerRole; @@ -390,6 +391,20 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } + // Make the art half-size if it has a small number of pixels + if (this->role == MasterRole || this->role == SmallArtRole) { + int p = 0; + for (int i = 0; i < length; i++) { + CRGB c = strip.getPixelColor(i++); // i advances by 2 + CRGB c2 = strip.getPixelColor(i); + nblend(c, c2, 128); + if (this->role == MasterRole) { + nblend(c, CRGB::Black, 128); + } + strip.setPixelColor(p++, c); + } + } + if (this->flashColor) { if (flashTimer.ended()) this->flashColor = 0; diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 14fb9f8ecc..3b8b6c359f 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -11,13 +11,14 @@ #define BUTTON_PIN_2 71 // SKIP #define BUTTON_PIN_3 72 // SET COLOR #define BUTTON_PIN_4 23 // TAP -#define BUTTON_PIN_5 73 +#define BUTTON_PIN_5 25 // "NEXT!" class Master { public: uint8_t taps=0; Timer tapTimer; + Timer perTapTimer; uint16_t tapTime[16]; Background background; @@ -50,9 +51,13 @@ class Master { } } - if (this->taps && this->tapTimer.since_mark() > 10000) { + if (this->taps && this->perTapTimer.ended()) { + if (this->taps == 2) { + this->ok(); + } else { + this->fail(); + } this->taps = 0; - this->fail(); } } @@ -72,7 +77,7 @@ class Master { if (button == 0) return; - if (button == 1) { + if (button == 4) { Serial.println((char *)F("Skip >>")); this->controller->force_next(); this->ok(); @@ -113,6 +118,7 @@ class Master { if (!this->taps) { this->tapTimer.start(0); } + this->perTapTimer.start(1000); uint32_t time = this->tapTimer.since_mark(); this->tapTime[this->taps++] = time; @@ -141,6 +147,8 @@ class Master { this->controller->set_tapped_bpm(bpm); this->ok(); + } else if (this->taps >= 2) { + this->controller->set_tapped_bpm(this->controller->current_state.bpm, taps-1); } } @@ -151,16 +159,16 @@ class Master { this->displayPalette(this->background); } else { uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; - strip.setPixelColor(16 - beat_pos, CRGB::White); + strip.setPixelColor(15 - beat_pos, CRGB::White); } } void displayProgress(uint8_t progress) { for (int i = 0; i < 16; i++) { if (i < progress % 16) { - strip.setPixelColor(16 - i, CRGB(128,128,128)); + strip.setPixelColor(15 - i, CRGB(128,128,128)); } else { - strip.setPixelColor(16 - i, CRGB::Black); + strip.setPixelColor(15 - i, CRGB::Black); } } } From 5be4a0c593f80855b90aec25c051680f127c9f9e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 18:19:22 -0700 Subject: [PATCH 094/263] Fix updater mode --- usermods/Tubes/Tubes.h | 20 +++++++++++++++----- usermods/Tubes/controller.h | 32 ++++++++++++++++++++++++++++++-- usermods/Tubes/firmware.sh | 5 ++++- usermods/Tubes/updater.h | 15 ++++++++++++--- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 7930e6b80c..13b79354ad 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -91,7 +91,7 @@ class TubesUsermod : public Usermod { // Blink when there's a connected client if (apActive) { strip.setPixelColor(0, CRGB::Purple); - if (millis() % 10000 > 1000 && WiFi.softAPgetStationNum()) { + if (millis() % 4000 > 1000 && WiFi.softAPgetStationNum()) { strip.setPixelColor(0, CRGB::Black); } strip.setPixelColor(1, CRGB::Black); @@ -100,17 +100,27 @@ class TubesUsermod : public Usermod { bool handleButton(uint8_t b) { // Special code for handling the "power save" button - if (b == 100) { + if (b == 100) { // Press button 0 for WLED_LONG_POWER_SAVE ms this->controller.togglePowerSave(); return true; } - if (b == 101) { + if (b == 101) { // Short press button 0 (piggybacks with default) this->controller.cancelOverrides(); return true; } - if (b == 102) { + if (b == 102) { // Double-click button 0 this->controller.acknowledge(); - this->controller.request_new_bpm(); + if (this->controller.isSelecting()) { + if (apActive) { + // Reboot, to turn off WiFi + doReboot = true; + } else { + // Turn on WiFi for updating/comms + this->controller.updater.ready(); + } + } else { + this->controller.request_new_bpm(); + } return true; } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 404f19c409..c54a09db0a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -110,6 +110,7 @@ class PatternController : public MessageReceiver { Timer paletteOverrideTimer; Timer patternOverrideTimer; Timer flashTimer; + Timer selectTimer; #ifdef USELCD Lcd *lcd; @@ -150,7 +151,7 @@ class PatternController : public MessageReceiver { } bool isMaster() { - return this->role == MasterRole; + return this->role >= MasterRole; } void setup() @@ -246,6 +247,14 @@ class PatternController : public MessageReceiver { this->patternOverrideTimer.stop(); } + void enterSelectMode() { + this->selectTimer.start(20000); + } + + bool isSelecting() { + return !this->selectTimer.ended(); + } + void set_palette_override(uint8_t value) { if (value == this->paletteOverride) return; @@ -932,6 +941,9 @@ class PatternController : public MessageReceiver { case 'U': case 'V': + case '*': + case '(': + case ')': case 'G': case 'A': case 'W': @@ -977,7 +989,8 @@ class PatternController : public MessageReceiver { Serial.println("U - begin auto-update"); Serial.println("O - offer an auto-update"); Serial.println("==== global actions ===="); - Serial.println("A - turn on access point"); + Serial.println("* - enter select mode (double-click to Ready)"); + Serial.println("A - turn on access point (Ready to update)"); Serial.println("W - forget WiFi client"); Serial.println("X - restart"); Serial.println("V### - auto-upgrade to version"); @@ -1138,6 +1151,21 @@ class PatternController : public MessageReceiver { this->cancelOverrides(); break; + case 'S': + Serial.println("enter select mode"); + this->enterSelectMode(); + break; + + case '*': + case '(': + this->enterSelectMode(); + break; + + case ')': + this->updater.stop(); + WiFi.softAPdisconnect(true); + break; + case 'V': // Version check: prepare for update if (this->updater.current_version.version >= action->arg) diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 5d204756ee..133d647990 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -16,6 +16,10 @@ EOF } update_config() { + # No longer update configs + return; + + echo "Updating configuration via OTA" curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" >/dev/null curl -s http://$1/reset >/dev/null } @@ -25,7 +29,6 @@ update_firmware() { curl -s -F "update=@../../build_output/firmware/esp32_quinled_uno.bin" $1/update >/dev/null echo "Updated; wait..." sleep 5 - echo "Updating configuration via OTA" update_config $1 } diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index c2f727c578..937e3b012f 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 5 +#define RELEASE_VERSION 6 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { @@ -113,7 +113,10 @@ class AutoUpdater { log("ready for update - turning on updater AP"); strcpy(apSSID, "WLED-UPDATE"); strcpy(apPass, "update1234"); - WLED::instance().initAP(true); + auto tmp = apBehavior; + apBehavior = AP_BEHAVIOR_ALWAYS; + WLED::instance().initAP(); + apBehavior = tmp; this->status = Ready; } @@ -123,6 +126,8 @@ class AutoUpdater { strcpy(clientPass, _storedPass.c_str()); strcpy(apSSID, _storedAPSSID.c_str()); strcpy(apPass, _storedAPPass.c_str()); + WiFi.softAPdisconnect(true); + apActive = false; WiFi.disconnect(false, true); WLED::instance().enableWatchdog(); this->status = Idle; @@ -132,6 +137,9 @@ class AutoUpdater { CRGB c; switch (this->status) { case Ready: + // Once the updater connects, no need to show ready + if (WiFi.softAPgetStationNum()) + return; c = CRGB::Purple; break; @@ -156,7 +164,7 @@ class AutoUpdater { default: return; } - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 10; i++) { strip.setPixelColor(i, c); } } @@ -199,6 +207,7 @@ class AutoUpdater { case WL_CONNECTED: if (WiFi.SSID() != String(this->current_version.ssid)) { log("disconnecting from WiFi"); + WiFi.softAPdisconnect(true); WiFi.disconnect(false, true); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; return; From e1347ed904a76da6710101ea7ee2562e98528f4f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 19:34:19 -0700 Subject: [PATCH 095/263] Sound reactive --- usermods/Tubes/controller.h | 9 +++++++ usermods/Tubes/debug.h | 3 +++ usermods/Tubes/particle.h | 7 ++++++ usermods/Tubes/sound.h | 47 +++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 usermods/Tubes/sound.h diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c54a09db0a..5f519f35b5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -4,6 +4,7 @@ #include "wled.h" #include "FX.h" #include "updater.h" +#include "sound.h" #include "beats.h" @@ -104,6 +105,7 @@ class PatternController : public MessageReceiver { uint8_t flashColor = 0; AutoUpdater updater = AutoUpdater(); + Sounder sound = Sounder(); Timer graphicsTimer; Timer updateTimer; @@ -196,6 +198,8 @@ class PatternController : public MessageReceiver { this->set_wled_palette(0); // Default palette this->set_wled_pattern(0, 128, 128); // Default pattern + this->sound.setup(); + this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); } @@ -298,6 +302,9 @@ class PatternController : public MessageReceiver { // Update the mesh this->node->update(); + // Update sound meter + this->sound.update(); + // Update patterns to the beat this->update_beat(); @@ -383,6 +390,8 @@ class PatternController : public MessageReceiver { } } + this->sound.handleOverlayDraw(); + // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles if (this->power_save && this->role < InstallationRole) { diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index efb1d7876b..571b07b0e3 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -80,6 +80,9 @@ class DebugController { if (this->controller->isMaster()) { Serial.print("PRIMARY "); } + if (this->controller->sound.active) { + Serial.print("SOUND "); + } Serial.printf("role=%d power_save=%d\n", this->controller->role, this->controller->power_save diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index fb0ba887ee..da0d026957 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -10,6 +10,7 @@ class Particle; typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); +uint8_t particleVolume = 64; // Default mic average is around 64 during music extern void drawPoint(Particle *particle, WS2812FX* leds); extern void drawFlash(Particle *particle, WS2812FX* leds); @@ -48,6 +49,9 @@ class Particle { void update(BeatFrame_24_8 frame) { + // Particles get brighter with the beat + this->brightness = (scale8(particleVolume, 80) + 170) << 8; + this->age = frame - this->born; this->position = this->udelta16(this->position, this->velocity); this->velocity = this->delta16(this->velocity, this->gravity); @@ -204,6 +208,9 @@ void drawPoint(Particle *particle, WS2812FX* leds) { } void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + // Bump up the radius with any beats + radius += scale8(particleVolume, 8) - 2; + auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; diff --git a/usermods/Tubes/sound.h b/usermods/Tubes/sound.h new file mode 100644 index 0000000000..79a559911a --- /dev/null +++ b/usermods/Tubes/sound.h @@ -0,0 +1,47 @@ + +#include "wled.h" +#include "fcn_declare.h" +#include "particle.h" + + +class Sounder { + public: + bool active = false; + uint8_t volume; + + void setup() { + this->active = true; + } + + void update() { + if (!this->active) { + particleVolume = 128; // Average volume + return; + } + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + this->active = false; + return; + } + + float volumeSmth = *(float*) um_data->u_data[0]; + this->volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. + particleVolume = this->volume; + } + + void handleOverlayDraw() { + if (!this->active) + return; + + int len = scale8(this->volume, 32); + for (int i = 0; i < len; i++) { + strip.setPixelColor(i, CRGB::White); + } + } + + static float mapf(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; + } + +}; From fcc2e619895a469c7970d8c6b8094d842eb6177a Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 19:59:53 -0700 Subject: [PATCH 096/263] better sound overlay --- usermods/Tubes/controller.h | 20 +++++++++++++++----- usermods/Tubes/effects.h | 1 + usermods/Tubes/particle.h | 12 +++++++----- usermods/Tubes/sound.h | 17 ++++++++++++----- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 5f519f35b5..8bb933d5ca 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -978,6 +978,16 @@ class PatternController : public MessageReceiver { break; } + case 'O': { + // Toggle power save + Action action = { + .key = command[0], + .arg = !this->sound.overlay + }; + this->broadcast_action(action); + break; + } + case 'r': this->setRole((ControllerRole)(arg >> 8)); return; @@ -996,7 +1006,7 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); Serial.println("@ - toggle power saving mode"); Serial.println("U - begin auto-update"); - Serial.println("O - offer an auto-update"); + Serial.println("O - toggle all sound overlays"); Serial.println("==== global actions ===="); Serial.println("* - enter select mode (double-click to Ready)"); Serial.println("A - turn on access point (Ready to update)"); @@ -1010,10 +1020,6 @@ class PatternController : public MessageReceiver { this->updater.start(); return; - case 'O': - broadcast_autoupdate(); - return; - case 0: // Empty command return; @@ -1123,6 +1129,10 @@ class PatternController : public MessageReceiver { WLED::instance().initAP(true); return; + case 'O': + this->sound.overlay = (action->arg != 0); + return; + case 'X': doReboot = true; return; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index bdb59fe957..22881c771b 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -151,6 +151,7 @@ static const EffectDef gEffects[] = { {{Bubble, Brighten}, {MediumDuration, Chill}}, {{Bubble, Brighten}, {MediumDuration, Chill}}, {{Bubble, Darken}, {MediumDuration, Chill}}, + {{Bubble, Invert}, {MediumDuration, Chill}}, {{Bubble, Invert}, {MediumDuration, Chill}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, MediumEnergy}}, diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index da0d026957..2db9189e61 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -7,10 +7,12 @@ #define MAX_PARTICLES 80 #undef PARTICLE_PALETTES +#define DEFAULT_PARTICLE_VOLUME 64 // Default mic average is around 64 during music + class Particle; typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); -uint8_t particleVolume = 64; // Default mic average is around 64 during music +uint8_t particleVolume = DEFAULT_PARTICLE_VOLUME; extern void drawPoint(Particle *particle, WS2812FX* leds); extern void drawFlash(Particle *particle, WS2812FX* leds); @@ -208,9 +210,6 @@ void drawPoint(Particle *particle, WS2812FX* leds) { } void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { - // Bump up the radius with any beats - radius += scale8(particleVolume, 8) - 2; - auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; @@ -242,7 +241,10 @@ void drawBeatbox(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); - uint8_t radius = 5; + uint8_t radius = 3; + + // Bump up the radius with any beats + radius += scale8(particleVolume, 8); drawRadius(particle, leds, pos, radius, c, false); } diff --git a/usermods/Tubes/sound.h b/usermods/Tubes/sound.h index 79a559911a..02280170c5 100644 --- a/usermods/Tubes/sound.h +++ b/usermods/Tubes/sound.h @@ -6,26 +6,27 @@ class Sounder { public: - bool active = false; + bool active = true; + bool overlay = false; uint8_t volume; void setup() { - this->active = true; } void update() { if (!this->active) { - particleVolume = 128; // Average volume + particleVolume = DEFAULT_PARTICLE_VOLUME; // Average volume return; } um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { this->active = false; + this->overlay = false; return; } - float volumeSmth = *(float*) um_data->u_data[0]; + float volumeSmth = *(float*)um_data->u_data[0]; this->volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. particleVolume = this->volume; } @@ -33,10 +34,16 @@ class Sounder { void handleOverlayDraw() { if (!this->active) return; + if (!this->overlay) + return; int len = scale8(this->volume, 32); + + Segment& segment = strip.getMainSegment(); + for (int i = 0; i < len; i++) { - strip.setPixelColor(i, CRGB::White); + uint32_t color = segment.color_from_palette(i, true, true, 255, 192); + strip.setPixelColor(i, color); } } From 39bd106ec03d88a592411857cafec2b2fc99f946 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 20:09:55 -0700 Subject: [PATCH 097/263] Better selection mode --- usermods/Tubes/Tubes.h | 11 ++++------- usermods/Tubes/controller.h | 39 ++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 13b79354ad..773f259e08 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -111,13 +111,10 @@ class TubesUsermod : public Usermod { if (b == 102) { // Double-click button 0 this->controller.acknowledge(); if (this->controller.isSelecting()) { - if (apActive) { - // Reboot, to turn off WiFi - doReboot = true; - } else { - // Turn on WiFi for updating/comms - this->controller.updater.ready(); - } + if (this->controller.isSelected()) + this->controller.deselect(); + else + this->controller.select(); } else { this->controller.request_new_bpm(); } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8bb933d5ca..35e745ad32 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -259,6 +259,23 @@ class PatternController : public MessageReceiver { return !this->selectTimer.ended(); } + bool isSelected() { + return this->updater.status == Ready; + } + + void select(bool selected = true) { + if (selected) + this->updater.ready(); + else { + this->updater.stop(); + WiFi.softAPdisconnect(true); + } + } + + void deselect() { + select(false); + } + void set_palette_override(uint8_t value) { if (value == this->paletteOverride) return; @@ -1134,10 +1151,14 @@ class PatternController : public MessageReceiver { return; case 'X': + if (!this->isSelected()) + return; doReboot = true; return; case 'R': + if (!this->isSelected()) + return; setRole((ControllerRole)(action->arg)); return; @@ -1170,19 +1191,15 @@ class PatternController : public MessageReceiver { this->cancelOverrides(); break; - case 'S': - Serial.println("enter select mode"); - this->enterSelectMode(); - break; - case '*': case '(': + Serial.println("enter select mode"); this->enterSelectMode(); break; case ')': - this->updater.stop(); - WiFi.softAPdisconnect(true); + Serial.println("exit select mode"); + this->deselect(); break; case 'V': @@ -1190,13 +1207,13 @@ class PatternController : public MessageReceiver { if (this->updater.current_version.version >= action->arg) break; - this->updater.ready(); + this->select(); break; case 'U': - if (this->updater.status == Ready) { - this->updater.start(); - } + if (!this->isSelected()) + return; + this->updater.start(); break; } From e5e81fd5d32985d40c2fcd63ee7999ca97a02a05 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 20:10:24 -0700 Subject: [PATCH 098/263] Playa-ready verison = 10 --- usermods/Tubes/updater.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 937e3b012f..cbf95d2b83 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 6 +#define RELEASE_VERSION 10 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { From afbde13c6ed21d0ff9bf056108a5fb9450e550e5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 20:16:55 -0700 Subject: [PATCH 099/263] Draw the overlay -after- power save --- usermods/Tubes/controller.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 35e745ad32..bb8997370c 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -407,8 +407,6 @@ class PatternController : public MessageReceiver { } } - this->sound.handleOverlayDraw(); - // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles if (this->power_save && this->role < InstallationRole) { @@ -420,6 +418,8 @@ class PatternController : public MessageReceiver { } } + this->sound.handleOverlayDraw(); + // Draw effects layers over whatever WLED is doing. // But not in manual (WLED) mode if (!this->patternOverride) { From 2ac6ce9d565d14443aa2f218daa5e69e5e391e1d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Aug 2022 01:08:54 -0700 Subject: [PATCH 100/263] Lower power requirements? --- usermods/Tubes/controller.h | 32 ++++++++++++++++---------------- usermods/Tubes/firmware.sh | 6 +++--- wled00/FX_fcn.cpp | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index bb8997370c..a16e66b0b1 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,8 +15,8 @@ #include "global_state.h" #include "node.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 100; -const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 80; +const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; #define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE #define STATUS_UPDATE_PERIOD 2000 @@ -165,25 +165,25 @@ class PatternController : public MessageReceiver { this->role = UnknownRole; } EEPROM.end(); - Serial.printf("Role = %d", this->role); + Serial.printf("Role = %d\n", this->role); this->power_save = (this->role < CampRole); - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.debugging = false; - switch (role) { - case MasterRole: - this->node->reset(4050); // MASTER ID - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; - break; + if (this->role <= CampRole) + strip.ablMilliampsMax = 700; // Really limit for batteries + else if (this->role <= InstallationRole) + strip.ablMilliampsMax = 1000; + else + strip.ablMilliampsMax = 1400; - case LegacyRole: + if (this->role >= MasterRole) { + this->node->reset(3850 + this->role); // MASTER ID + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + } else if (this->role >= LegacyRole) { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - break; - - default: + } else { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - break; } + this->options.debugging = false; this->load_options(this->options); #ifdef USELCD @@ -201,7 +201,7 @@ class PatternController : public MessageReceiver { this->sound.setup(); this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to - Serial.println("Patterns: ok"); + Serial.println("Controller: ok"); } void do_pattern_changes() { diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 133d647990..b8b5d81698 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -16,8 +16,8 @@ EOF } update_config() { - # No longer update configs - return; + # No longer update configs? comment this + # return; echo "Updating configuration via OTA" curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" >/dev/null @@ -56,7 +56,7 @@ update_one() { } update_batch() { - airport -s | grep WLED | cut -c23-32 | while read line + airport -s | grep WLED | cut -c10-32 | while read line do if [ "$line" == "WLED-AP" ]; then update_one 4.3.2.1 "$line" "wled1234" diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 191c813ac1..e192f95b44 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1001,7 +1001,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i) //I am NOT to be held liable for burned down garages! //fine tune power estimation constants for your setup -#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) +#define MA_FOR_ESP 300 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) //you can set it to 0 if the ESP is powered by USB and the LEDs by external void WS2812FX::estimateCurrentAndLimitBri() { From a362459306ff897fa8492da12d618ccdd84706cf Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Aug 2022 09:18:03 -0700 Subject: [PATCH 101/263] misc --- usermods/Tubes/controller.h | 63 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a16e66b0b1..03d2c08a88 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -22,7 +22,7 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; #define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 4 -#define MAX_COLOR_CHANGE_PHRASES 20 +#define MAX_COLOR_CHANGE_PHRASES 10 #define ROLE_EEPROM_LOCATION 2559 @@ -97,6 +97,7 @@ class PatternController : public MessageReceiver { uint8_t num_leds; VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; + bool canOverride = false; uint8_t paletteOverride = 0; uint8_t patternOverride = 0; uint16_t wled_fader = 0; @@ -277,6 +278,8 @@ class PatternController : public MessageReceiver { } void set_palette_override(uint8_t value) { + if (!this->canOverride) + return; if (value == this->paletteOverride) return; @@ -292,6 +295,8 @@ class PatternController : public MessageReceiver { } void set_pattern_override(uint8_t value, uint8_t auto_mode) { + if (!this->canOverride) + return; if (value == DEFAULT_WLED_FX && !this->patternOverride) return; if (value == this->patternOverride) @@ -328,19 +333,21 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); // Detect manual overrides & update the current state to match. - if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { - this->set_palette_override(0); - } else if (segment.palette != this->current_state.palette_id) { - this->set_palette_override(segment.palette); - } - - uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; - if (wled_mode < 10) - wled_mode = DEFAULT_WLED_FX; - if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { - this->set_pattern_override(0, wled_mode); - } else if (segment.mode != wled_mode) { - this->set_pattern_override(segment.mode, wled_mode); + if (this->canOverride) { + if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { + this->set_palette_override(0); + } else if (segment.palette != this->current_state.palette_id) { + this->set_palette_override(segment.palette); + } + + uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (wled_mode < 10) + wled_mode = DEFAULT_WLED_FX; + if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { + this->set_pattern_override(0, wled_mode); + } else if (segment.mode != wled_mode) { + this->set_pattern_override(segment.mode, wled_mode); + } } do_pattern_changes(); @@ -352,7 +359,7 @@ class PatternController : public MessageReceiver { // Update current status if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!this->node->is_following() || random(0, 5) == 0) { + if (!this->node->is_following() || random(0, 4) == 0) { this->send_update(); } } @@ -427,13 +434,13 @@ class PatternController : public MessageReceiver { } // Make the art half-size if it has a small number of pixels - if (this->role == MasterRole || this->role == SmallArtRole) { + if (this->role >= MasterRole || this->role == SmallArtRole) { int p = 0; for (int i = 0; i < length; i++) { CRGB c = strip.getPixelColor(i++); // i advances by 2 CRGB c2 = strip.getPixelColor(i); nblend(c, c2, 128); - if (this->role == MasterRole) { + if (this->role >= MasterRole) { nblend(c, CRGB::Black, 128); } strip.setPixelColor(p++, c); @@ -491,12 +498,14 @@ class PatternController : public MessageReceiver { void update_beat() { this->current_state.bpm = this->next_state.bpm = this->beats->bpm; this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) - if (this->current_state.bpm>>8 >= 125) + if (this->current_state.bpm>>8 <= 118) // Hip hop / ghettofunk + this->energy = MediumEnergy; + else if (this->current_state.bpm>>8 >= 125) // House & breaks this->energy = HighEnergy; - else if (this->current_state.bpm>>8 > 120) + else if (this->current_state.bpm>>8 > 120) // Tech house this->energy = MediumEnergy; else - this->energy = Chill; + this->energy = Chill; // Deep house } void send_update() { @@ -600,8 +609,8 @@ class PatternController : public MessageReceiver { case ExtraShortDuration: return random8(2, 6); case ShortDuration: return random8(5,15); case MediumDuration: return random8(15,25); - case LongDuration: return random8(35,45); - case ExtraLongDuration: return random8(70, 100); + case LongDuration: return random8(20,40); + case ExtraLongDuration: return random8(25, 60); } return 5; } @@ -621,6 +630,8 @@ class PatternController : public MessageReceiver { // Don't select the built-in palettes this->next_state.palette_id = random8(6, gGradientPaletteCount); auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); + + // Change color more often in boring patterns if (this->isBoring) { phrases /= 2; } @@ -660,11 +671,11 @@ class PatternController : public MessageReceiver { this->next_state.effect_params = def.params; switch (def.control.duration) { - case ExtraShortDuration: return random(2,3); + case ExtraShortDuration: return random(1,3); case ShortDuration: return random(2,4); - case MediumDuration: return random(4,8); - case LongDuration: return random(6, 14); - case ExtraLongDuration: return random(10,20); + case MediumDuration: return random(4,7); + case LongDuration: return random(8, 11); + case ExtraLongDuration: return random(10,15); } return 1; } From 753e276cc76f8ad8cf891f151bb308e8712e8d18 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Aug 2022 09:22:52 -0700 Subject: [PATCH 102/263] manual mode protection --- usermods/Tubes/controller.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 03d2c08a88..2c9324c411 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -332,6 +332,10 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); + // You can only go into manual control after enabling the wifi + if (apActive && this->updater.status != Ready) + this->canOverride = true; + // Detect manual overrides & update the current state to match. if (this->canOverride) { if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { From 0f9bc57f6aab343b5892a9407932478d591a75b8 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 1 Sep 2022 20:12:54 -0700 Subject: [PATCH 103/263] better taps? and art car brightness --- usermods/Tubes/controller.h | 6 +++++- usermods/Tubes/master.h | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 2c9324c411..b8bc645017 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,8 +15,9 @@ #include "global_state.h" #include "node.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 80; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; +const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; #define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE #define STATUS_UPDATE_PERIOD 2000 @@ -27,6 +28,7 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; #define ROLE_EEPROM_LOCATION 2559 #define IDENTIFY_STUCK_PATTERNS +#define IDENTIFY_STUCK_PALETTES typedef struct { bool debugging; @@ -181,6 +183,8 @@ class PatternController : public MessageReceiver { this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; } else if (this->role >= LegacyRole) { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + } else if (this->role == InstallationRole) { + this->options.brightness = DEFAULT_TANK_BRIGHTNESS; } else { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; } diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 3b8b6c359f..b341b73481 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -114,11 +114,10 @@ class Master { } void tap() { - Serial.println((char *)F("tap")); if (!this->taps) { this->tapTimer.start(0); } - this->perTapTimer.start(1000); + this->perTapTimer.start(1500); uint32_t time = this->tapTimer.since_mark(); this->tapTime[this->taps++] = time; @@ -127,13 +126,22 @@ class Master { if (this->taps > 4) { // Can study this later to make BPM detection better - bpm = 60000*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat + // Should be 60000; fudge a bit to adjust to real-world timings + bpm = 60220*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat if (bpm < 70*256) bpm *= 2; else if (bpm > 140*256) bpm /= 2; } - + + Serial.printf("tap %d: ", this->taps); + Serial.print(bpm >> 8); + uint8_t f = scale8(100, bpm & 0xFF); + Serial.print("."); + if (f < 10) + Serial.print("0"); + Serial.println(f); + if (this->taps == 16) { Serial.println("OK! taps"); this->taps = 0; From 698a26ee6299f4926a676324df49e5731c8537d4 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 7 Sep 2022 13:55:12 -0700 Subject: [PATCH 104/263] Fix after merge --- wled00/FX.cpp | 49 --------------------------------- wled00/FX.h | 64 -------------------------------------------- wled00/FX_fcn.cpp | 3 ++- wled00/data/index.js | 3 ++- wled00/json.cpp | 33 ----------------------- 5 files changed, 4 insertions(+), 148 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ad3fac55ee..540a1ad3e3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7512,53 +7512,4 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_DJLIGHT, &mode_DJLight, _data_FX_MODE_DJLIGHT); addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); -<<<<<<< HEAD } - - - - -#define MAX_VIRTUAL_LEDS 150 -uint8_t noise[MAX_VIRTUAL_LEDS]; - -void fillnoise8(uint32_t frame, uint8_t num_leds) { - uint16_t scale = 17; - uint8_t dataSmoothing = 240; - - for (int i = 0; i < num_leds; i++) { - uint8_t data = inoise8(i * scale, frame>>2); - - // The range of the inoise8 function is roughly 16-238. - // These two operations expand those values out to roughly 0..255 - data = qsub8(data,16); - data = qadd8(data,scale8(data,39)); - - uint8_t olddata = noise[i]; - uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); - noise[i] = newdata; - } -} - -uint16_t WS2812FX::mode_tubes_moise(void) { - uint16_t pixelLen = SEGLEN > MAX_VIRTUAL_LEDS ? MAX_VIRTUAL_LEDS : SEGLEN; - // uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 - // if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - // uint32_t* pixels = reinterpret_cast(SEGENV.data); - uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); - uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; - - // generate noise data - fillnoise8(now>>4, pixelLen); - - uint16_t offset = 0; - for (int i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(noise[i], true, PALETTE_SOLID_WRAP, 0)); - } - - return FRAMETIME; -} -======= - //addEffect(FX_MODE_CUSTOMEFFECT, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT); //WLEDSR Custom Effects -#endif // USERMOD_AUDIOREACTIVE -} ->>>>>>> 0a7756d3 (fix post merge) diff --git a/wled00/FX.h b/wled00/FX.h index a9187ae0f8..ddb4bd1225 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -275,7 +275,6 @@ #define FX_MODE_2DMETABALLS 142 // non audio #define FX_MODE_2DPULSER 143 // non audio #define FX_MODE_2DDRIFT 144 // non audio -<<<<<<< HEAD #define FX_MODE_2DWAVERLY 145 // audio enhanced #define FX_MODE_2DSWIRL 146 // audio enhanced #define FX_MODE_2DAKEMI 147 // audio enhanced @@ -309,69 +308,6 @@ #define FX_MODE_ROCKTAVES 174 // audio enhanced #define MODE_COUNT 175 -======= -#endif -#ifndef WLED_DISABLE_AUDIO - #ifndef WLED_DISABLE_2D - #define FX_MODE_2DWAVERLY 145 // audio enhanced - #define FX_MODE_2DSWIRL 146 // audio enhanced - #define FX_MODE_2DAKEMI 147 // audio enhanced - // 148 & 149 reserved - #endif - #define FX_MODE_PIXELWAVE 150 // audio enhanced - #define FX_MODE_JUGGLES 151 // audio enhanced - #define FX_MODE_MATRIPIX 152 // audio enhanced - #define FX_MODE_GRAVIMETER 153 // audio enhanced - #define FX_MODE_PLASMOID 154 // audio enhanced - #define FX_MODE_PUDDLES 155 // audio enhanced - #define FX_MODE_MIDNOISE 156 // audio enhanced - #define FX_MODE_NOISEMETER 157 // audio enhanced - #define FX_MODE_NOISEFIRE 158 // audio enhanced - #define FX_MODE_PUDDLEPEAK 159 // audio enhanced - #define FX_MODE_RIPPLEPEAK 160 // audio enhanced - #define FX_MODE_GRAVCENTER 161 // audio enhanced - #define FX_MODE_GRAVCENTRIC 162 // audio enhanced -#endif - -#ifndef USERMOD_AUDIOREACTIVE - - #ifndef WLED_DISABLE_AUDIO - #define MODE_COUNT 163 - #else - #ifndef WLED_DISABLE_2D - #define MODE_COUNT 145 - #else - #define MODE_COUNT 118 - #endif - #endif - -#else - - #ifdef WLED_DISABLE_AUDIO - #error Incompatible options: WLED_DISABLE_AUDIO and USERMOD_AUDIOREACTIVE - #endif - #ifdef WLED_DISABLE_2D - #error AUDIOREACTIVE usermod requires 2D support. - #endif - #define FX_MODE_2DGEQ 148 - #define FX_MODE_2DFUNKYPLANK 149 - #define FX_MODE_PIXELS 163 - #define FX_MODE_FREQWAVE 164 - #define FX_MODE_FREQMATRIX 165 - #define FX_MODE_WATERFALL 166 - #define FX_MODE_FREQPIXELS 167 - #define FX_MODE_BINMAP 168 - #define FX_MODE_NOISEMOVE 169 - #define FX_MODE_FREQMAP 170 - #define FX_MODE_GRAVFREQ 171 - #define FX_MODE_DJLIGHT 172 - #define FX_MODE_BLURZ 173 - #define FX_MODE_ROCKTAVES 174 - //#define FX_MODE_CUSTOMEFFECT 175 //WLEDSR Custom Effects - - #define MODE_COUNT 175 -#endif ->>>>>>> d4cc6df2 (Handle manual override of colors) typedef enum mapping1D2D { M12_Pixels = 0, diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e192f95b44..a9c3a88529 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -203,6 +203,7 @@ void Segment::setUpLeds() { else #endif leds = (CRGB*)malloc(sizeof(CRGB)*length()); + } } CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { @@ -1001,7 +1002,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i) //I am NOT to be held liable for burned down garages! //fine tune power estimation constants for your setup -#define MA_FOR_ESP 300 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) +#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) //you can set it to 0 if the ESP is powered by USB and the LEDs by external void WS2812FX::estimateCurrentAndLimitBri() { diff --git a/wled00/data/index.js b/wled00/data/index.js index 4ae487bee0..4c4721faf8 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2103,7 +2103,8 @@ function setFX(ind = null) d.querySelector(`#fxlist input[name="fx"][value="${ind}"]`).checked = true; } - var obj = {"seg": {"fx": parseInt(ind),"fxdef":1}}; // fxdef sets effect parameters to default values, TODO add client setting + // STEVEE: Removed "fxdef:1" + var obj = {"seg": {"fx": parseInt(ind)}}; // fxdef sets effect parameters to default values, TODO add client setting requestJson(obj); } diff --git a/wled00/json.cpp b/wled00/json.cpp index c9e5eacf3d..c7d583e161 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -178,7 +178,6 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) seg.startTransition(strip.getTransition()); // set effect transitions //seg.markForReset(); seg.mode = fx; -<<<<<<< HEAD } } @@ -202,38 +201,6 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) if (sOpt != seg.palette) { if (strip.paletteFade && !seg.transitional) seg.startTransition(strip.getTransition()); seg.palette = sOpt; -======= - // load default values from effect string if effect is selected without - // any other effect parameter (i.e. effect clicked in UI) - if ( elem[F("sx")].isNull() - && elem[F("ix")].isNull() - && elem["pal"].isNull() - && elem[F("c1")].isNull() - && elem[F("c2")].isNull() - && elem[F("c3")].isNull() - && false // TEMPORARILY DISABLED, I don't want it in my code- SteveE - ) - { - int16_t sOpt; - sOpt = extractModeDefaults(fx, SET_F("sx")); if (sOpt >= 0) seg.speed = sOpt; - sOpt = extractModeDefaults(fx, SET_F("ix")); if (sOpt >= 0) seg.intensity = sOpt; - sOpt = extractModeDefaults(fx, SET_F("c1")); if (sOpt >= 0) seg.custom1 = sOpt; - sOpt = extractModeDefaults(fx, SET_F("c2")); if (sOpt >= 0) seg.custom2 = sOpt; - sOpt = extractModeDefaults(fx, SET_F("c3")); if (sOpt >= 0) seg.custom3 = sOpt; - sOpt = extractModeDefaults(fx, SET_F("mp12")); if (sOpt >= 0) seg.map1D2D = sOpt & 0x03; - sOpt = extractModeDefaults(fx, SET_F("ssim")); if (sOpt >= 0) seg.soundSim = sOpt & 0x07; - sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) seg.reverse = (bool)sOpt; - sOpt = extractModeDefaults(fx, SET_F("mi")); if (sOpt >= 0) seg.mirror = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, SET_F("rY")); if (sOpt >= 0) seg.reverse_y = (bool)sOpt; - sOpt = extractModeDefaults(fx, SET_F("mY")); if (sOpt >= 0) seg.mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "pal"); - if (sOpt >= 0 && sOpt < strip.getPaletteCount() + strip.customPalettes.size()) { - if (sOpt != seg.palette) { - if (strip.paletteFade && !seg.transitional) seg.startTransition(strip.getTransition()); - seg.palette = sOpt; - } - } ->>>>>>> 4a7b0f57 (Clean up unused) } } } From 8b03af770690585e6af6dc466e25846229a90625 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 4 Jul 2022 20:41:02 -0700 Subject: [PATCH 105/263] Initial port of Tubes - palettes and noise functions --- wled00/FX.cpp | 46 + wled00/palettes.h | 2119 +++++++++++++++++++++++++++++++++++---------- wled00/wled.h | 4 + 3 files changed, 1733 insertions(+), 436 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index fbe72a89eb..92885ad399 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4504,6 +4504,7 @@ uint16_t mode_aurora(void) { return FRAMETIME; } +<<<<<<< HEAD static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;sx=24,pal=50,1d"; // WLED-SR effects @@ -7517,3 +7518,48 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); } +======= + + + + +#define MAX_VIRTUAL_LEDS 150 +uint8_t noise[MAX_VIRTUAL_LEDS]; + +void fillnoise8(uint32_t frame, uint8_t num_leds) { + uint16_t scale = 17; + uint8_t dataSmoothing = 240; + + for (int i = 0; i < num_leds; i++) { + uint8_t data = inoise8(i * scale, frame>>2); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + data = qsub8(data,16); + data = qadd8(data,scale8(data,39)); + + uint8_t olddata = noise[i]; + uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); + noise[i] = newdata; + } +} + +uint16_t WS2812FX::mode_tubes_moise(void) { + uint16_t pixelLen = SEGLEN > MAX_VIRTUAL_LEDS ? MAX_VIRTUAL_LEDS : SEGLEN; + // uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 + // if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + // uint32_t* pixels = reinterpret_cast(SEGENV.data); + uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); + uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; + + // generate noise data + fillnoise8(now>>4, pixelLen); + + uint16_t offset = 0; + for (int i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(noise[i], true, PALETTE_SOLID_WRAP, 0)); + } + + return FRAMETIME; +} +>>>>>>> 3b8e5a2f (Initial port of Tubes - palettes and noise functions) diff --git a/wled00/palettes.h b/wled00/palettes.h index 5e524059d3..5bb34f13e7 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -13,7 +13,17 @@ #ifndef PalettesWLED_h #define PalettesWLED_h -const byte ib_jul01_gp[] PROGMEM = { +// Redefine FastLED's gradient palette declaration: +#define DEFINE_PALETTE(X) const byte X[] PROGMEM = + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +// Gradient palette "ib_jul01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 bytes of program space. + +DEFINE_PALETTE(ib_jul01_gp) { 0, 194, 1, 1, 94, 1, 29, 18, 132, 57,131, 28, @@ -24,20 +34,19 @@ const byte ib_jul01_gp[] PROGMEM = { // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_vintage_57_gp[] PROGMEM = { +DEFINE_PALETTE( es_vintage_57_gp ) { 0, 2, 1, 1, 53, 18, 1, 0, 104, 69, 29, 1, 153, 167,135, 10, 255, 46, 56, 4}; - // Gradient palette "es_vintage_01_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte es_vintage_01_gp[] PROGMEM = { +DEFINE_PALETTE( es_vintage_01_gp ) { 0, 4, 1, 1, 51, 16, 0, 1, 76, 97,104, 3, @@ -47,92 +56,124 @@ const byte es_vintage_01_gp[] PROGMEM = { 229, 4, 1, 1, 255, 4, 1, 1}; - // Gradient palette "es_rivendell_15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_rivendell_15_gp[] PROGMEM = { +DEFINE_PALETTE( es_rivendell_15_gp ) { 0, 1, 14, 5, 101, 16, 36, 14, 165, 56, 68, 30, 242, 150,156, 99, 255, 150,156, 99}; - // Gradient palette "rgi_15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -// Edited to be brighter - -const byte rgi_15_gp[] PROGMEM = { - 0, 4, 1, 70, - 31, 55, 1, 30, - 63, 255, 4, 7, - 95, 59, 2, 29, - 127, 11, 3, 50, - 159, 39, 8, 60, - 191, 112, 19, 40, - 223, 78, 11, 39, - 255, 29, 8, 59}; +DEFINE_PALETTE( rgi_15_gp ) { + 0, 4, 1, 31, + 31, 55, 1, 16, + 63, 197, 3, 7, + 95, 59, 2, 17, + 127, 6, 2, 34, + 159, 39, 6, 33, + 191, 112, 13, 32, + 223, 56, 9, 35, + 255, 22, 6, 38}; // Gradient palette "retro2_16_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 8 bytes of program space. -const byte retro2_16_gp[] PROGMEM = { +DEFINE_PALETTE( retro2_16_gp ) { 0, 188,135, 1, 255, 46, 7, 1}; - // Gradient palette "Analogous_1_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Analogous_1_gp[] PROGMEM = { +DEFINE_PALETTE( Analogous_1_gp ) { 0, 3, 0,255, 63, 23, 0,255, 127, 67, 0,255, 191, 142, 0, 45, 255, 255, 0, 0}; - // Gradient palette "es_pinksplash_08_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte es_pinksplash_08_gp[] PROGMEM = { +DEFINE_PALETTE( es_pinksplash_08_gp ) { 0, 126, 11,255, 127, 197, 1, 22, 175, 210,157,172, 221, 157, 3,112, 255, 157, 3,112}; +// Gradient palette "es_pinksplash_07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_pinksplash_07_gp ) { + 0, 229, 1, 1, + 61, 242, 4, 63, + 101, 255, 12,255, + 127, 249, 81,252, + 153, 255, 11,235, + 193, 244, 5, 68, + 255, 232, 1, 5}; + +// Gradient palette "Coral_reef_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Coral_reef.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( Coral_reef_gp ) { + 0, 40,199,197, + 50, 10,152,155, + 96, 1,111,120, + 96, 43,127,162, + 139, 10, 73,111, + 255, 1, 34, 71}; + +// Gradient palette "es_ocean_breeze_068_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_068.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_068_gp ) { + 0, 100,156,153, + 51, 1, 99,137, + 101, 1, 68, 84, + 104, 35,142,168, + 178, 0, 63,117, + 255, 1, 10, 10}; // Gradient palette "es_ocean_breeze_036_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 16 bytes of program space. -const byte es_ocean_breeze_036_gp[] PROGMEM = { +DEFINE_PALETTE( es_ocean_breeze_036_gp ) { 0, 1, 6, 7, 89, 1, 99,111, 153, 144,209,255, 255, 0, 73, 82}; - // Gradient palette "departure_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 88 bytes of program space. -const byte departure_gp[] PROGMEM = { +DEFINE_PALETTE( departure_gp ) { 0, 8, 3, 0, 42, 23, 7, 0, 63, 75, 38, 6, @@ -146,13 +187,12 @@ const byte departure_gp[] PROGMEM = { 212, 0, 55, 0, 255, 0, 55, 0}; - // Gradient palette "es_landscape_64_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -const byte es_landscape_64_gp[] PROGMEM = { +DEFINE_PALETTE( es_landscape_64_gp ) { 0, 0, 0, 0, 37, 2, 25, 1, 76, 15,115, 5, @@ -163,13 +203,12 @@ const byte es_landscape_64_gp[] PROGMEM = { 204, 59,117,250, 255, 1, 37,192}; - // Gradient palette "es_landscape_33_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte es_landscape_33_gp[] PROGMEM = { +DEFINE_PALETTE( es_landscape_33_gp ) { 0, 1, 5, 0, 19, 32, 23, 1, 38, 161, 55, 1, @@ -177,13 +216,12 @@ const byte es_landscape_33_gp[] PROGMEM = { 66, 39,142, 74, 255, 1, 4, 1}; - // Gradient palette "rainbowsherbet_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte rainbowsherbet_gp[] PROGMEM = { +DEFINE_PALETTE( rainbowsherbet_gp ) { 0, 255, 33, 4, 43, 255, 68, 25, 86, 255, 7, 25, @@ -192,13 +230,12 @@ const byte rainbowsherbet_gp[] PROGMEM = { 209, 42,255, 22, 255, 87,255, 65}; - // Gradient palette "gr65_hult_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte gr65_hult_gp[] PROGMEM = { +DEFINE_PALETTE( gr65_hult_gp ) { 0, 247,176,247, 48, 255,136,255, 89, 220, 29,226, @@ -206,13 +243,12 @@ const byte gr65_hult_gp[] PROGMEM = { 216, 1,124,109, 255, 1,124,109}; - // Gradient palette "gr64_hult_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte gr64_hult_gp[] PROGMEM = { +DEFINE_PALETTE( gr64_hult_gp ) { 0, 1,124,109, 66, 1, 93, 79, 104, 52, 65, 1, @@ -222,13 +258,12 @@ const byte gr64_hult_gp[] PROGMEM = { 239, 0, 55, 45, 255, 0, 55, 45}; - // Gradient palette "GMT_drywet_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte GMT_drywet_gp[] PROGMEM = { +DEFINE_PALETTE( GMT_drywet_gp ) { 0, 47, 30, 2, 42, 213,147, 24, 84, 103,219, 52, @@ -237,13 +272,12 @@ const byte GMT_drywet_gp[] PROGMEM = { 212, 1, 1,111, 255, 1, 7, 33}; - // Gradient palette "ib15_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte ib15_gp[] PROGMEM = { +DEFINE_PALETTE( ib15_gp ) { 0, 113, 91,147, 72, 157, 88, 78, 89, 208, 85, 33, @@ -251,26 +285,35 @@ const byte ib15_gp[] PROGMEM = { 141, 137, 31, 39, 255, 59, 33, 89}; - -// Gradient palette "Tertiary_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/vermillion/tn/Tertiary_01.png.index.html +// Gradient palette "Fuschia_7_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/fuschia/tn/Fuschia-7.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Tertiary_01_gp[] PROGMEM = { - 0, 0, 1,255, - 63, 3, 68, 45, - 127, 23,255, 0, - 191, 100, 68, 1, - 255, 255, 1, 4}; +DEFINE_PALETTE( Fuschia_7_gp ) { + 0, 43, 3,153, + 63, 100, 4,103, + 127, 188, 5, 66, + 191, 161, 11,115, + 255, 135, 20,182}; + +// Gradient palette "es_emerald_dragon_08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 16 bytes of program space. +DEFINE_PALETTE( es_emerald_dragon_08_gp ) { + 0, 97,255, 1, + 101, 47,133, 1, + 178, 13, 43, 1, + 255, 2, 10, 1}; // Gradient palette "lava_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 52 bytes of program space. -const byte lava_gp[] PROGMEM = { +DEFINE_PALETTE( lava_gp ) { 0, 0, 0, 0, 46, 18, 0, 0, 96, 113, 0, 0, @@ -285,28 +328,26 @@ const byte lava_gp[] PROGMEM = { 244, 255,255, 71, 255, 255,255,255}; - -// Gradient palette "fierce_ice_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fierce-ice.png.index.html +// Gradient palette "fire_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fire.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte fierce_ice_gp[] PROGMEM = { - 0, 0, 0, 0, - 59, 0, 9, 45, - 119, 0, 38,255, - 149, 3,100,255, - 180, 23,199,255, - 217, 100,235,255, +DEFINE_PALETTE( fire_gp ) { + 0, 1, 1, 0, + 76, 32, 5, 0, + 146, 192, 24, 0, + 197, 220,105, 5, + 240, 252,255, 31, + 250, 252,255,111, 255, 255,255,255}; - // Gradient palette "Colorfull_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 44 bytes of program space. -const byte Colorfull_gp[] PROGMEM = { +DEFINE_PALETTE( Colorfull_gp ) { 0, 10, 85, 5, 25, 29,109, 18, 60, 59,138, 42, @@ -319,13 +360,26 @@ const byte Colorfull_gp[] PROGMEM = { 168, 100,180,155, 255, 22,121,174}; +// Gradient palette "Magenta_Evening_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Magenta_Evening.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( Magenta_Evening_gp ) { + 0, 71, 27, 39, + 31, 130, 11, 51, + 63, 213, 2, 64, + 70, 232, 1, 66, + 76, 252, 1, 69, + 108, 123, 2, 51, + 255, 46, 9, 35}; // Gradient palette "Pink_Purple_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 44 bytes of program space. -const byte Pink_Purple_gp[] PROGMEM = { +DEFINE_PALETTE( Pink_Purple_gp ) { 0, 19, 2, 39, 25, 26, 4, 45, 51, 33, 6, 52, @@ -338,13 +392,12 @@ const byte Pink_Purple_gp[] PROGMEM = { 183, 128, 57,155, 255, 146, 40,123}; - // Gradient palette "Sunset_Real_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte Sunset_Real_gp[] PROGMEM = { +DEFINE_PALETTE( Sunset_Real_gp ) { 0, 120, 0, 0, 22, 179, 22, 0, 51, 255,104, 0, @@ -353,74 +406,12 @@ const byte Sunset_Real_gp[] PROGMEM = { 198, 16, 0,130, 255, 0, 0,160}; - -// Gradient palette "Sunset_Yellow_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 44 bytes of program space. - -const byte Sunset_Yellow_gp[] PROGMEM = { - 0, 10, 62,123, - 36, 56,130,103, - 87, 153,225, 85, - 100, 199,217, 68, - 107, 255,207, 54, - 115, 247,152, 57, - 120, 239,107, 61, - 128, 247,152, 57, - 180, 255,207, 54, - 223, 255,227, 48, - 255, 255,248, 42}; - - -// Gradient palette "Beech_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Beech.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 60 bytes of program space. - -const byte Beech_gp[] PROGMEM = { - 0, 255,252,214, - 12, 255,252,214, - 22, 255,252,214, - 26, 190,191,115, - 28, 137,141, 52, - 28, 112,255,205, - 50, 51,246,214, - 71, 17,235,226, - 93, 2,193,199, - 120, 0,156,174, - 133, 1,101,115, - 136, 1, 59, 71, - 136, 7,131,170, - 208, 1, 90,151, - 255, 0, 56,133}; - - -// Gradient palette "Another_Sunset_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Another_Sunset.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte Another_Sunset_gp[] PROGMEM = { - 0, 110, 49, 11, - 29, 55, 34, 10, - 68, 22, 22, 9, - 68, 239,124, 8, - 97, 220,156, 27, - 124, 203,193, 61, - 178, 33, 53, 56, - 255, 0, 1, 52}; - - - - - // Gradient palette "es_autumn_19_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 52 bytes of program space. -const byte es_autumn_19_gp[] PROGMEM = { +DEFINE_PALETTE( es_autumn_19_gp ) { 0, 26, 1, 1, 51, 67, 4, 1, 84, 118, 14, 1, @@ -435,13 +426,12 @@ const byte es_autumn_19_gp[] PROGMEM = { 249, 17, 1, 1, 255, 17, 1, 1}; - // Gradient palette "BlacK_Blue_Magenta_White_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte BlacK_Blue_Magenta_White_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Blue_Magenta_White_gp ) { 0, 0, 0, 0, 42, 0, 0, 45, 84, 0, 0,255, @@ -450,26 +440,24 @@ const byte BlacK_Blue_Magenta_White_gp[] PROGMEM = { 212, 255, 55,255, 255, 255,255,255}; - // Gradient palette "BlacK_Magenta_Red_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte BlacK_Magenta_Red_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Magenta_Red_gp ) { 0, 0, 0, 0, 63, 42, 0, 45, 127, 255, 0,255, 191, 255, 0, 45, 255, 255, 0, 0}; - // Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { +DEFINE_PALETTE( BlacK_Red_Magenta_Yellow_gp ) { 0, 0, 0, 0, 42, 42, 0, 0, 84, 255, 0, 0, @@ -478,377 +466,1632 @@ const byte BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { 212, 255, 55, 45, 255, 255,255, 0}; - // Gradient palette "Blue_Cyan_Yellow_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 20 bytes of program space. -const byte Blue_Cyan_Yellow_gp[] PROGMEM = { +DEFINE_PALETTE( Blue_Cyan_Yellow_gp ) { 0, 0, 0,255, 63, 0, 55,255, 127, 0,255,255, 191, 42,255, 45, 255, 255,255, 0}; +// Gradient palette "Sunset_Yellow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( Sunset_Yellow_gp ) { + 0, 10, 62,123, + 36, 56,130,103, + 87, 153,225, 85, + 100, 199,217, 68, + 107, 255,207, 54, + 115, 247,152, 57, + 120, 239,107, 61, + 128, 247,152, 57, + 180, 255,207, 54, + 223, 255,227, 48, + 255, 255,248, 42}; + -//Custom palette by Aircoookie - -const byte Orange_Teal_gp[] PROGMEM = { - 0, 0,150, 92, - 55, 0,150, 92, - 200, 255, 72, 0, - 255, 255, 72, 0}; - -//Custom palette by Aircoookie - -const byte Tiamat_gp[] PROGMEM = { - 0, 1, 2, 14, //gc - 33, 2, 5, 35, //gc from 47, 61,126 - 100, 13,135, 92, //gc from 88,242,247 - 120, 43,255,193, //gc from 135,255,253 - 140, 247, 7,249, //gc from 252, 69,253 - 160, 193, 17,208, //gc from 231, 96,237 - 180, 39,255,154, //gc from 130, 77,213 - 200, 4,213,236, //gc from 57,122,248 - 220, 39,252,135, //gc from 177,254,255 - 240, 193,213,253, //gc from 203,239,253 - 255, 255,249,255}; - -//Custom palette by Aircoookie - -const byte April_Night_gp[] PROGMEM = { - 0, 1, 5, 45, //deep blue - 10, 1, 5, 45, - 25, 5,169,175, //light blue - 40, 1, 5, 45, - 61, 1, 5, 45, - 76, 45,175, 31, //green - 91, 1, 5, 45, - 112, 1, 5, 45, - 127, 249,150, 5, //yellow - 143, 1, 5, 45, - 162, 1, 5, 45, - 178, 255, 92, 0, //pastel orange - 193, 1, 5, 45, - 214, 1, 5, 45, - 229, 223, 45, 72, //pink - 244, 1, 5, 45, - 255, 1, 5, 45}; - -const byte Orangery_gp[] PROGMEM = { - 0, 255, 95, 23, - 30, 255, 82, 0, - 60, 223, 13, 8, - 90, 144, 44, 2, - 120, 255,110, 17, - 150, 255, 69, 0, - 180, 158, 13, 11, - 210, 241, 82, 17, - 255, 213, 37, 4}; - -//inspired by Mark Kriegsman https://gist.github.com/kriegsman/756ea6dcae8e30845b5a -const byte C9_gp[] PROGMEM = { - 0, 184, 4, 0, //red - 60, 184, 4, 0, - 65, 144, 44, 2, //amber - 125, 144, 44, 2, - 130, 4, 96, 2, //green - 190, 4, 96, 2, - 195, 7, 7, 88, //blue - 255, 7, 7, 88}; - -const byte Sakura_gp[] PROGMEM = { - 0, 196, 19, 10, - 65, 255, 69, 45, - 130, 223, 45, 72, - 195, 255, 82,103, - 255, 223, 13, 17}; - -const byte Aurora_gp[] PROGMEM = { - 0, 1, 5, 45, //deep blue - 64, 0,200, 23, - 128, 0,255, 0, //green - 170, 0,243, 45, - 200, 0,135, 7, - 255, 1, 5, 45};//deep blue - -const byte Atlantica_gp[] PROGMEM = { - 0, 0, 28,112, //#001C70 - 50, 32, 96,255, //#2060FF - 100, 0,243, 45, - 150, 12, 95, 82, //#0C5F52 - 200, 25,190, 95, //#19BE5F - 255, 40,170, 80};//#28AA50 - - const byte C9_2_gp[] PROGMEM = { - 0, 6, 126, 2, //green - 45, 6, 126, 2, - 45, 4, 30, 114, //blue - 90, 4, 30, 114, - 90, 255, 5, 0, //red - 135, 255, 5, 0, - 135, 196, 57, 2, //amber - 180, 196, 57, 2, - 180, 137, 85, 2, //yellow - 255, 137, 85, 2}; - - //C9, but brighter and with a less purple blue - const byte C9_new_gp[] PROGMEM = { - 0, 255, 5, 0, //red - 60, 255, 5, 0, - 60, 196, 57, 2, //amber (start 61?) - 120, 196, 57, 2, - 120, 6, 126, 2, //green (start 126?) - 180, 6, 126, 2, - 180, 4, 30, 114, //blue (start 191?) - 255, 4, 30, 114}; - -// Gradient palette "temperature_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/arendal/tn/temperature.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 144 bytes of program space. - -const byte temperature_gp[] PROGMEM = { - 0, 1, 27,105, - 14, 1, 40,127, - 28, 1, 70,168, - 42, 1, 92,197, - 56, 1,119,221, - 70, 3,130,151, - 84, 23,156,149, - 99, 67,182,112, - 113, 121,201, 52, - 127, 142,203, 11, - 141, 224,223, 1, - 155, 252,187, 2, - 170, 247,147, 1, - 184, 237, 87, 1, - 198, 229, 43, 1, - 226, 171, 2, 2, - 240, 80, 3, 3, - 255, 80, 3, 3}; - - const byte Aurora2_gp[] PROGMEM = { - 0, 17, 177, 13, //Greenish - 64, 121, 242, 5, //Greenish - 128, 25, 173, 121, //Turquoise - 192, 250, 77, 127, //Pink - 255, 171, 101, 221 //Purple - }; - - // Gradient palette "bhw1_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_01.png.index.html +// Gradient palette "cloud_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/cloud.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 12 bytes of program space. -const byte retro_clown_gp[] PROGMEM = { - 0, 227,101, 3, - 117, 194, 18, 19, - 255, 92, 8,192}; +DEFINE_PALETTE( cloud_gp ) { + 0, 247,149, 91, + 127, 208, 32, 71, + 255, 42, 79,188}; -// Gradient palette "bhw1_04_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_04.png.index.html + +// Gradient palette "fireandice_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/fireandice.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. +// Size: 24 bytes of program space. -const byte candy_gp[] PROGMEM = { - 0, 229,227, 1, - 15, 227,101, 3, - 142, 40, 1, 80, - 198, 17, 1, 79, - 255, 0, 0, 45}; +DEFINE_PALETTE( fireandice_gp ) { + 0, 80, 2, 1, + 51, 206, 15, 1, + 101, 242, 34, 1, + 153, 16, 67,128, + 204, 2, 21, 69, + 255, 1, 2, 4}; -// Gradient palette "bhw1_05_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_05.png.index.html +// Gradient palette "bhw2_39_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_39.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 8 bytes of program space. +// Size: 28 bytes of program space. + +DEFINE_PALETTE( bhw2_39_gp ) { + 0, 2,184,188, + 33, 56, 27,162, + 66, 56, 27,162, + 122, 255,255, 45, + 150, 227, 65, 6, + 201, 67, 13, 27, + 255, 16, 1, 53}; + +// Gradient palette "tashangel_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/tashangel.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. -const byte toxy_reaf_gp[] PROGMEM = { - 0, 1,221, 53, - 255, 73, 3,178}; +DEFINE_PALETTE( tashangel_gp ) { + 0, 133, 68,197, + 51, 2, 1, 33, + 101, 50, 35,130, + 153, 199,225,237, + 204, 41,187,228, + 255, 133, 68,197}; -// Gradient palette "bhw1_06_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_06.png.index.html +// Gradient palette "butterflytalker_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/butterflytalker.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. +// Size: 28 bytes of program space. -const byte fairy_reaf_gp[] PROGMEM = { - 0, 184, 1,128, - 160, 1,193,182, - 219, 153,227,190, - 255, 255,255,255}; +DEFINE_PALETTE( butterflytalker_gp ) { + 0, 1, 1, 6, + 51, 6, 11, 52, + 89, 107,107,192, + 127, 101,161,192, + 165, 107,107,192, + 204, 6, 11, 52, + 255, 0, 0, 0}; -// Gradient palette "bhw1_14_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_14.png.index.html +// Gradient palette "os250k_metres_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/os/tn/os250k-metres.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( os250k_metres_gp ) { + 0, 255,255,255, + 11, 255,255,255, + 11, 255,252,214, + 34, 255,252,214, + 34, 255,248,178, + 57, 255,248,178, + 57, 255,211,130, + 81, 255,211,130, + 81, 255,176, 89, + 115, 255,176, 89, + 115, 255,147, 63, + 173, 255,147, 63, + 173, 255,127, 55, + 255, 255,127, 55}; + +// Gradient palette "Night_Midnight_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Night_Midnight.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 36 bytes of program space. -const byte semi_blue_gp[] PROGMEM = { +DEFINE_PALETTE( Night_Midnight_gp ) { + 0, 15, 25, 27, + 36, 22, 48, 91, + 59, 32, 80,203, + 74, 110,154,228, + 77, 255,255,255, + 82, 110,154,228, + 96, 32, 80,203, + 189, 5, 18, 73, + 255, 0, 1, 12}; + +// Gradient palette "Afterdusk_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Afterdusk.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( Afterdusk_gp ) { 0, 0, 0, 0, - 12, 1, 1, 3, - 53, 8, 1, 22, - 80, 4, 6, 89, - 119, 2, 25,216, - 145, 7, 10, 99, - 186, 15, 2, 31, - 233, 2, 1, 5, - 255, 0, 0, 0}; + 25, 1, 1, 1, + 48, 1, 1, 1, + 67, 41, 49, 52, + 70, 210,219,216, + 73, 155,115,137, + 81, 109, 46, 78, + 86, 109, 46, 78, + 97, 109, 46, 78, + 165, 50, 15, 79, + 255, 16, 1, 80}; + +// Gradient palette "BlueSky_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/BlueSky.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. -// Gradient palette "bhw1_three_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_three.png.index.html +DEFINE_PALETTE( BlueSky_gp ) { + 0, 1, 7, 39, + 25, 2, 25, 88, + 61, 9, 53,160, + 88, 46,115,201, + 102, 120,203,245, + 108, 88,169,230, + 124, 63,139,216, + 216, 21, 96,203, + 255, 2, 60,188}; + +// Gradient palette "Gold_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Gold_Orange.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. +// Size: 52 bytes of program space. -const byte pink_candy_gp[] PROGMEM = { - 0, 255,255,255, - 45, 7, 12,255, - 112, 227, 1,127, - 112, 227, 1,127, - 140, 255,255,255, - 155, 227, 1,127, - 196, 45, 1, 99, - 255, 255,255,255}; +DEFINE_PALETTE( Gold_Orange_gp ) { + 0, 244, 88, 11, + 21, 247,118, 26, + 40, 249,152, 50, + 62, 252,201, 82, + 72, 255,255,125, + 79, 255,211,119, + 83, 255,169,112, + 87, 255,211,119, + 94, 255,255,125, + 103, 244,207, 54, + 118, 237,164, 16, + 202, 242,124, 13, + 255, 244, 88, 11}; + +// Gradient palette "Analogous_02_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/vermillion/tn/Analogous_02.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Analogous_02_gp ) { + 0, 32, 0,123, + 63, 110, 5, 79, + 127, 255, 23, 45, + 191, 255, 21, 30, + 255, 255, 18, 18}; -// Gradient palette "bhw1_w00t_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_w00t.png.index.html +// Gradient palette "Analogous_04a_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/pink/tn/Analogous_04a.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. +// Size: 36 bytes of program space. -const byte red_reaf_gp[] PROGMEM = { - 0, 3, 13, 43, - 104, 78,141,240, - 188, 255, 0, 0, - 255, 28, 1, 1}; +DEFINE_PALETTE( Analogous_04a_gp ) { + 0, 67, 55,255, + 42, 67, 55,255, + 84, 67, 55,255, + 84, 120, 33,255, + 127, 120, 33,255, + 170, 120, 33,255, + 170, 255, 23, 45, + 212, 255, 23, 45, + 255, 255, 23, 45}; + +// Gradient palette "Cyan_Orange_Stripped_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Cyan_Orange_Stripped.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 48 bytes of program space. + +DEFINE_PALETTE( Cyan_Orange_Stripped_gp ) { + 0, 1,108,212, + 60, 1,108,212, + 121, 1,108,212, + 121, 0, 0, 0, + 124, 0, 0, 0, + 127, 0, 0, 0, + 127, 229,127, 15, + 188, 242,186, 92, + 248, 255,255,255, + 248, 0, 0, 0, + 251, 0, 0, 0, + 255, 0, 0, 0}; +// Gradient palette "Cyan_White_Green_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Cyan_White_Green.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Cyan_White_Green_gp ) { + 0, 0,255,255, + 63, 42,255,255, + 127, 255,255,255, + 191, 42,255, 45, + 255, 0,255, 0}; -// Gradient palette "bhw2_23_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_23.png.index.html +// Gradient palette "Wild_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/mp/tn/Wild_Orange.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Red & Flash in SR -// Size: 28 bytes of program space. +// Size: 56 bytes of program space. -const byte aqua_flash_gp[] PROGMEM = { +DEFINE_PALETTE( Wild_Orange_gp ) { 0, 0, 0, 0, - 66, 57,227,233, - 96, 255,255, 8, - 124, 255,255,255, - 153, 255,255, 8, - 188, 57,227,233, - 255, 0, 0, 0}; + 0, 144, 11, 1, + 0, 144, 11, 1, + 5, 144, 11, 1, + 10, 194, 36, 1, + 30, 252, 79, 1, + 86, 249,175,100, + 106, 244,122, 25, + 124, 237, 79, 1, + 157, 244,154, 2, + 196, 252,255, 5, + 209, 252,223, 3, + 239, 255,108, 1, + 255, 255, 36, 1}; + +// Gradient palette "IKat_Radial_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/mp/tn/IKat_Radial.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. -// Gradient palette "bhw2_xc_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_xc.png.index.html +DEFINE_PALETTE( IKat_Radial_gp ) { + 0, 3, 7, 4, + 56, 255,255,255, + 127, 3, 7, 4, + 196, 255,255,255, + 255, 3, 7, 4}; + +// Gradient palette "Citrus_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Citrus.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// YBlue in SR -// Size: 28 bytes of program space. +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Citrus_gp ) { + 0, 252,164, 5, + 63, 149,25, 3, + 135, 255,166, 9, + 201, 147,39, 3, + 255, 237,119, 4}; + +// Gradient palette "Teal_Blue_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Teal_Blue.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Teal_Blue_gp ) { + 0, 1, 73, 88, + 63, 1, 43, 52, + 127, 1, 77, 95, + 196, 1, 58, 67, + 255, 1, 45, 50}; + +// Gradient palette "Ldby_Orange_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/lb/misc/tn/Ldby_Orange.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Ldby_Orange_gp ) { + 0, 217, 45, 17, + 61, 179, 21, 8, + 130, 222, 49, 21, + 193, 203, 32, 7, + 255, 173, 22, 6}; + +// Gradient palette "purple_orange_d07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/purple-orange-d07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( purple_orange_d07_gp ) { + 0, 53, 27, 91, + 36, 53, 27, 91, + 36, 121, 55,111, + 72, 121, 55,111, + 72, 179,107,137, + 109, 179,107,137, + 109, 179,189,182, + 145, 179,189,182, + 145, 234,152, 59, + 182, 234,152, 59, + 182, 227, 92, 11, + 218, 227, 92, 11, + 218, 165, 40, 1, + 255, 165, 40, 1}; + +// Gradient palette "blue_tan_d08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/blue-tan-d08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( blue_tan_d08_gp ) { + 0, 7, 77,210, + 31, 7, 77,210, + 31, 21,112,216, + 63, 21,112,216, + 63, 53,149,207, + 95, 53,149,207, + 95, 123,180,192, + 127, 123,180,192, + 127, 186,186,127, + 159, 186,186,127, + 159, 182,159, 50, + 191, 182,159, 50, + 191, 155,117, 14, + 223, 155,117, 14, + 223, 115, 72, 2, + 255, 115, 72, 2}; + +// Gradient palette "green_purple_d07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/green-purple-d07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( green_purple_d07_gp ) { + 0, 1, 90, 12, + 36, 1, 90, 12, + 36, 12,147, 51, + 72, 12,147, 51, + 72, 56,189,120, + 109, 56,189,120, + 109, 179,189,182, + 145, 179,189,182, + 145, 179,107,137, + 182, 179,107,137, + 182, 121, 55,111, + 218, 121, 55,111, + 218, 53, 27, 91, + 255, 53, 27, 91}; + +// Gradient palette "knoza_00_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/knoza/tn/knoza-00.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( knoza_00_gp ) { + 0, 56, 56,237, + 1, 115, 1, 1, + 24, 115, 1, 1, + 25, 237,130, 46, + 101, 237,186, 1, + 113, 237,186, 1, + 115, 2, 1, 1, + 138, 2, 1, 1, + 139, 237,186, 1, + 153, 237,186, 1, + 228, 237,130, 46, + 229, 115, 1, 1, + 253, 115, 1, 1, + 255, 56, 56,237}; +// Gradient palette "knoza_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/knoza/tn/knoza-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( knoza_18_gp ) { + 0, 8, 1, 1, + 2, 1,239, 1, + 51, 1,239, 1, + 52, 175,130, 1, + 100, 175,130, 1, + 101, 1, 1, 1, + 115, 1, 1, 1, + 117, 237,239,237, + 138, 237,239,237, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 175,130, 1, + 203, 175,130, 1, + 203, 1,239, 1, + 252, 1,239, 1, + 255, 8, 1, 1}; + +// Gradient palette "calpan_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/calpan/tn/calpan-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 40 bytes of program space. + +DEFINE_PALETTE( calpan_18_gp ) { + 0, 133, 31,137, + 1, 117, 2, 88, + 24, 117, 2, 88, + 25, 239,241,245, + 32, 239,241,245, + 51, 239,241,245, + 53, 117, 2, 88, + 76, 117, 2, 88, + 77, 133, 31,137, + 255, 239,241,245}; + +// Gradient palette "calbayo_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/calbayo/tn/calbayo-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( calbayo_18_gp ) { + 0, 210,131, 1, + 60, 210,131, 1, + 62, 41, 2, 3, + 99, 41, 2, 3, + 100, 106, 40, 1, + 101, 210,131, 1, + 126, 210,131, 1, + 127, 210, 31, 6, + 165, 210, 31, 6, + 166, 210,131, 1, + 188, 210,131, 1, + 191, 3, 6, 6, + 226, 3, 6, 6, + 228, 210,131, 1, + 253, 210,131, 1, + 255, 1, 58, 29}; + +// Gradient palette "fib53_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( fib53_15_gp ) { + 0, 239, 11, 31, + 101, 239, 11, 31, + 101, 239,241,240, + 127, 239,241,240, + 128, 1, 1, 1, + 152, 1, 1, 1, + 153, 239,241,240, + 178, 239,241,240, + 179, 239, 11, 31, + 202, 239, 11, 31, + 203, 239,241,240, + 229, 239,241,240, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "grindylow_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( grindylow_15_gp ) { + 0, 101,241,105, + 127, 26,182,105, + 255, 26,151, 80}; + +// Gradient palette "grindylow_21_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-21.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( grindylow_21_gp ) { + 0, 101,241,105, + 51, 60,241,240, + 128, 26,241,240, + 191, 26,182,105, + 255, 26,151, 80}; + +// Gradient palette "konjo_08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-08.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( konjo_08_gp ) { + 0, 213,229,240, + 127, 213,229,240, + 128, 133,168,188, + 150, 133,168,188, + 150, 21, 57, 91, + 152, 0, 6, 33, + 177, 0, 6, 33, + 179, 0, 2, 9, + 200, 0, 2, 9, + 203, 0, 6, 33, + 227, 0, 6, 33, + 229, 30, 0, 2, + 252, 30, 0, 2, + 255, 0, 6, 33}; + +// Gradient palette "konjo_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 88 bytes of program space. + +DEFINE_PALETTE( konjo_18_gp ) { + 0, 109, 5, 1, + 13, 109, 5, 1, + 14, 133,168,188, + 37, 133,168,188, + 39, 0, 6, 33, + 63, 0, 6, 33, + 63, 77,130,184, + 87, 77,130,184, + 89, 0, 2, 9, + 114, 0, 2, 9, + 115, 213,229,240, + 140, 213,229,240, + 142, 0, 2, 9, + 165, 0, 2, 9, + 166, 77,130,184, + 191, 77,130,184, + 193, 0, 6, 33, + 216, 0, 6, 33, + 217, 133,168,188, + 240, 133,168,188, + 241, 109, 5, 1, + 255, 109, 5, 1}; + +// Gradient palette "konjo_19_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-19.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 88 bytes of program space. + +DEFINE_PALETTE( konjo_19_gp ) { + 0, 109, 5, 1, + 13, 109, 5, 1, + 14, 133,168,188, + 37, 133,168,188, + 39, 0, 6, 33, + 63, 0, 6, 33, + 65, 109, 5, 1, + 87, 109, 5, 1, + 89, 0, 2, 9, + 114, 0, 2, 9, + 115, 213,229,240, + 140, 213,229,240, + 142, 0, 2, 9, + 165, 0, 2, 9, + 166, 109, 5, 1, + 192, 109, 5, 1, + 193, 0, 6, 33, + 216, 0, 6, 33, + 217, 133,168,188, + 240, 133,168,188, + 241, 109, 5, 1, + 255, 109, 5, 1}; + +// Gradient palette "konkikyo_19_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konkikyo/tn/konkikyo-19.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( konkikyo_19_gp ) { + 0, 1, 2, 9, + 101, 1, 2, 9, + 102, 199,213,252, + 122, 199,213,252, + 126, 1, 2, 9, + 151, 1, 2, 9, + 151, 24,128,245, + 177, 24,128,245, + 178, 1, 2, 9, + 203, 1, 2, 9, + 203, 177,133, 1, + 229, 177,133, 1, + 229, 1, 2, 9, + 252, 1, 2, 9, + 255, 1, 2, 9}; + +// Gradient palette "sulz_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_22_gp ) { + 0, 247, 54, 7, + 1, 247, 54, 7, + 24, 247, 54, 7, + 25, 1, 1, 1, + 51, 1, 1, 1, + 51, 247,248,247, + 75, 247,248,247, + 75, 247, 54, 7, + 101, 247, 54, 7, + 102, 1, 1, 1, + 115, 1, 1, 1, + 115, 247, 54, 7, + 138, 247, 54, 7, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 247, 54, 7, + 179, 247, 54, 7, + 181, 247,248,247, + 202, 247,248,247, + 203, 1, 1, 1, + 228, 1, 1, 1, + 228, 247, 54, 7, + 249, 247, 54, 7, + 255, 247, 54, 7}; + +// Gradient palette "Pills_2_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/icons/tn/Pills-2.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( Pills_2_gp ) { + 0, 192,147, 11, + 127, 148,104, 59, + 255, 109, 69,155}; + +// Gradient palette "Pink_Yellow_Orange_1_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/icons/tn/Pink-Yellow-Orange-1.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( Pink_Yellow_Orange_1_gp ) { + 0, 255,199, 0, + 34, 255,121, 0, + 106, 255, 63, 0, + 168, 194, 13, 6, + 255, 146, 1, 37}; +// Gradient palette "es_autumn_04_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_04.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_autumn_04_gp ) { + 0, 2, 1, 1, + 101, 27, 1, 0, + 165, 210, 22, 1, + 234, 255,166, 42, + 255, 255,166, 42}; + +// Gradient palette "es_autumn_02_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_02.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. -const byte yelblu_hot_gp[] PROGMEM = { - 0, 4, 2, 9, - 58, 16, 0, 47, - 122, 24, 0, 16, - 158, 144, 9, 1, - 183, 179, 45, 1, - 219, 220,114, 2, - 255, 234,237, 1}; +DEFINE_PALETTE( es_autumn_02_gp ) { + 0, 86, 6, 1, + 127, 255,255,125, + 153, 255,255,125, + 242, 194, 96, 1, + 255, 194, 96, 1}; - // Gradient palette "bhw2_45_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_45.png.index.html +// Gradient palette "es_candide_30_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/candide/tn/es_candide_30.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 24 bytes of program space. -const byte lite_light_gp[] PROGMEM = { - 0, 0, 0, 0, - 9, 1, 1, 1, - 40, 5, 5, 6, - 66, 5, 5, 6, - 101, 10, 1, 12, - 255, 0, 0, 0}; +DEFINE_PALETTE( es_candide_30_gp ) { + 0, 242,244,242, + 63, 133,255,137, + 127, 242,146,194, + 191, 104,187,245, + 252, 232,239,237, + 255, 232,239,237}; + +// Gradient palette "es_chic_16_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/chic/tn/es_chic_16.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. -// Gradient palette "bhw2_22_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_22.png.index.html +DEFINE_PALETTE( es_chic_16_gp ) { + 0, 4, 1, 1, + 51, 135, 99, 3, + 63, 222,248,160, + 76, 110,118, 50, + 89, 72, 55, 6, + 127, 4, 1, 1, + 165, 72, 55, 6, + 172, 90, 84, 22, + 178, 110,118, 50, + 191, 222,248,160, + 204, 135, 99, 3, + 247, 4, 1, 1, + 255, 4, 1, 1}; + +// Gradient palette "es_coffee_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/coffee/tn/es_coffee_01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( es_coffee_01_gp ) { + 0, 152,173,123, + 13, 152,154,106, + 25, 150,136, 91, + 63, 133, 78, 35, + 86, 112, 46, 15, + 114, 86, 15, 1, + 153, 68, 6, 1, + 178, 46, 1, 1, + 191, 31, 1, 1, + 216, 14, 1, 0, + 255, 6, 1, 0}; + +// Gradient palette "es_emerald_dragon_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_01.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Pink Plasma in SR // Size: 20 bytes of program space. -const byte red_flash_gp[] PROGMEM = { - 0, 0, 0, 0, - 99, 227, 1, 1, - 130, 249,199, 95, - 155, 227, 1, 1, - 255, 0, 0, 0}; +DEFINE_PALETTE( es_emerald_dragon_01_gp ) { + 0, 1, 1, 1, + 79, 1, 19, 7, + 130, 1, 59, 25, + 229, 28,255,255, + 255, 28,255,255}; + +// Gradient palette "es_landscape_57_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_57.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_57_gp ) { + 0, 27, 91, 0, + 89, 126,171,106, + 91, 157,199,255, + 143, 45,142,245, + 191, 3, 96,235, + 255, 1, 15, 22}; +// Gradient palette "es_landscape_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_22_gp ) { + 0, 1, 6, 1, + 38, 7, 49, 1, + 63, 21,124, 1, + 68, 173,244,252, + 127, 10,164,156, + 255, 5, 68, 66}; +// Gradient palette "es_landscape_47_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_47.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_47_gp ) { + 0, 175,125, 44, + 38, 88, 45, 3, + 58, 46, 27, 1, + 76, 20, 14, 0, + 79, 249,193,140, + 255, 121, 27, 1}; + +// Gradient palette "es_landscape_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( es_landscape_10_gp ) { + 0, 244,213, 55, + 24, 242,209, 53, + 51, 237,203, 51, + 63, 210,252,252, + 89, 171,225,230, + 127, 123,221,203, + 204, 25,122,144, + 255, 10, 93,115}; + +// Gradient palette "es_landscape_76_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_76.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_landscape_76_gp ) { + 0, 252,178, 82, + 127, 208, 91, 7, + 132, 153,173,188, + 191, 163,187,221, + 255, 130,191,250}; + +// Gradient palette "es_landscape_61_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_61.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_landscape_61_gp ) { + 0, 90,199, 1, + 89, 73,219, 6, + 127, 34,189, 6, + 128, 113,221, 75, + 130, 255,252,255, + 178, 64,189,255, + 255, 1,122,255}; + +// Gradient palette "es_landscape_60_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_60.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( es_landscape_60_gp ) { + 0, 161,112, 18, + 51, 130, 78, 1, + 89, 95, 59, 1, + 91, 133,151,140, + 136, 22, 92, 91, + 178, 1, 49, 52, + 242, 0, 1, 1, + 255, 0, 1, 1}; + +// Gradient palette "es_landscape_51_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_51.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( es_landscape_51_gp ) { + 0, 128,128,103, + 39, 165,161,144, + 76, 206,195,190, + 114, 15, 71,247, + 178, 1, 9, 71, + 255, 1, 1, 10}; + +// Gradient palette "es_landscape_06_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_06.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 12 bytes of program space. + +DEFINE_PALETTE( es_landscape_06_gp ) { + 0, 90,199, 1, + 89, 173,244,252, + 255, 57,175,207}; + +// Gradient palette "es_ocean_breeze_049_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_049.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. -// Gradient palette "bhw3_40_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_40.png.index.html +DEFINE_PALETTE( es_ocean_breeze_049_gp ) { + 0, 184,231,250, + 76, 0,112,203, + 77, 29,168,228, + 79, 179,235,255, + 153, 64,189,255, + 255, 0,124,199}; + +// Gradient palette "es_ocean_breeze_057_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_057.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 32 bytes of program space. -const byte blink_red_gp[] PROGMEM = { +DEFINE_PALETTE( es_ocean_breeze_057_gp ) { + 0, 115, 82, 49, + 76, 87, 51, 22, + 79, 249, 71, 9, + 101, 249,122, 17, + 140, 247,121, 38, + 178, 175,125, 71, + 229, 123,108, 83, + 255, 83, 97, 83}; + +// Gradient palette "es_ocean_breeze_074_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_074.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( es_ocean_breeze_074_gp ) { 0, 1, 1, 1, - 43, 4, 1, 11, - 76, 10, 1, 3, - 109, 161, 4, 29, - 127, 255, 86,123, - 165, 125, 16,160, - 204, 35, 13,223, - 255, 18, 2, 18}; - -// Gradient palette "bhw3_52_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_52.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Blue in SR + 101, 34, 23, 3, + 127, 53, 26, 2, + 130, 203, 65, 7, + 153, 78, 56, 8, + 191, 22, 37, 11, + 255, 1, 4, 1}; + +// Gradient palette "es_pinksplash_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. + +DEFINE_PALETTE( es_pinksplash_05_gp ) { + 0, 206, 1, 25, + 20, 192, 45, 82, + 38, 179,182,182, + 76, 206, 1, 25, + 127, 255,135,252, + 178, 206, 1, 25, + 216, 179,182,182, + 231, 192, 45, 82, + 255, 206, 1, 25}; + +// Gradient palette "es_pinksplash_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 28 bytes of program space. -const byte red_shift_gp[] PROGMEM = { - 0, 31, 1, 27, - 45, 34, 1, 16, - 99, 137, 5, 9, - 132, 213,128, 10, - 175, 199, 22, 1, - 201, 199, 9, 6, - 255, 1, 0, 1}; +DEFINE_PALETTE( es_pinksplash_10_gp ) { + 0, 26, 17, 27, + 63, 184, 1, 37, + 76, 234,141,174, + 89, 148, 2, 35, + 127, 26, 17, 27, + 252, 90, 65, 89, + 255, 90, 65, 89}; + +// Gradient palette "es_vintage_56_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_56.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. -// Gradient palette "bhw4_097_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_097.png.index.html +DEFINE_PALETTE( es_vintage_56_gp ) { + 0, 220,225,221, + 51, 83, 79, 7, + 109, 25, 0, 1, + 119, 255,131, 19, + 127, 217,221,184, + 135, 255,131, 19, + 145, 25, 0, 1, + 204, 60, 46, 1, + 255, 220,225,221}; + +// Gradient palette "es_vintage_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_10.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Red in SR -// Size: 44 bytes of program space. +// Size: 16 bytes of program space. + +DEFINE_PALETTE( es_vintage_10_gp ) { + 0, 1, 3, 1, + 51, 7, 1, 1, + 127, 112, 18, 0, + 255, 206,207,182}; -const byte red_tide_gp[] PROGMEM = { - 0, 247, 5, 0, - 28, 255, 67, 1, - 43, 234, 88, 11, - 58, 234,176, 51, - 84, 229, 28, 1, - 114, 113, 12, 1, - 140, 255,225, 44, - 168, 113, 12, 1, - 196, 244,209, 88, - 216, 255, 28, 1, - 255, 53, 1, 1}; - -// Gradient palette "bhw4_017_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_017.png.index.html +// Gradient palette "gold_yellow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/clth/tn/gold-yellow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( gold_yellow_gp ) { + 0, 0, 0, 0, + 94, 42, 29, 0, + 189, 255,135, 0, + 213, 255,189, 4, + 238, 255,255, 25, + 246, 255,255,103, + 255, 255,255,255}; + +// Gradient palette "radioactive_slime_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/faun/tn/radioactive-slime.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( radioactive_slime_gp ) { + 0, 0, 0, 0, + 25, 1, 4, 1, + 58, 1, 19, 1, + 76, 4, 30, 4, + 101, 17, 43, 13, + 118, 12, 69, 13, + 135, 8,100, 13, + 150, 27,146, 36, + 174, 59,199, 75, + 195, 135,195, 79, + 222, 255,189, 84, + 239, 255,221, 96, + 255, 255,255,111}; + +// Gradient palette "pastel_rainbow_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/othr/tn/pastel-rainbow.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( pastel_rainbow_gp ) { + 0, 0, 0, 0, + 33, 1, 2, 8, + 67, 7, 12, 45, + 88, 27, 18, 31, + 110, 67, 27, 19, + 129, 83, 38, 52, + 147, 100, 53,103, + 168, 90, 96, 93, + 189, 79,156, 83, + 206, 110,178,132, + 222, 148,203,197, + 238, 197,227,223, + 255, 255,255,255}; + +// Gradient palette "purple_sunset_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/othr/tn/purple-sunset.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( purple_sunset_gp ) { + 0, 0, 0, 0, + 31, 1, 1, 1, + 62, 1, 1, 7, + 62, 3, 2, 6, + 63, 6, 4, 5, + 88, 16, 8, 9, + 114, 31, 14, 15, + 131, 45, 22, 22, + 148, 61, 31, 31, + 152, 65, 39, 37, + 155, 69, 48, 45, + 192, 118, 86, 46, + 225, 184,135, 47, + 238, 197,161, 72, + 255, 213,187,103}; + +// Gradient palette "rainfall_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/jjg/misc/tn/rainfall.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( rainfall_gp ) { + 0, 192,118, 3, + 36, 192,118, 3, + 36, 222,118, 24, + 72, 222,118, 24, + 72, 224,209, 37, + 109, 224,209, 37, + 109, 58,159, 43, + 145, 58,159, 43, + 145, 7,133, 52, + 182, 7,133, 52, + 182, 4,118, 50, + 218, 4,118, 50, + 218, 1, 85, 8, + 255, 1, 85, 8}; + +// Gradient palette "sulz_12_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-12.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_12_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 17, 38, 14, + 45, 17, 38, 14, + 46, 1, 3, 10, + 69, 1, 3, 10, + 70, 17, 38, 14, + 91, 17, 38, 14, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 17, 38, 14, + 182, 17, 38, 14, + 183, 1, 3, 10, + 206, 1, 3, 10, + 206, 17, 38, 14, + 228, 17, 38, 14, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_10_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 117, 1,168, + 45, 117, 1,168, + 46, 1, 3, 10, + 69, 1, 3, 10, + 69, 117, 1,168, + 91, 117, 1,168, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 117, 1,168, + 182, 117, 1,168, + 183, 1, 3, 10, + 206, 1, 3, 10, + 206, 117, 1,168, + 229, 117, 1,168, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_15_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-15.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_15_gp ) { + 0, 247,229,247, + 2, 1, 3, 10, + 23, 1, 3, 10, + 23, 57, 1, 1, + 45, 57, 1, 1, + 46, 1, 3, 10, + 69, 1, 3, 10, + 69, 57, 1, 1, + 90, 57, 1, 1, + 92, 1, 3, 10, + 113, 1, 3, 10, + 115, 247,229,247, + 137, 247,229,247, + 140, 1, 3, 10, + 160, 1, 3, 10, + 160, 57, 1, 1, + 181, 57, 1, 1, + 183, 1, 3, 10, + 206, 1, 3, 10, + 207, 57, 1, 1, + 229, 57, 1, 1, + 229, 1, 3, 10, + 253, 1, 3, 10, + 255, 247,229,247}; + +// Gradient palette "sulz_21_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/sulz/tn/sulz-21.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( sulz_21_gp ) { + 0, 247,248, 7, + 1, 247,248, 7, + 23, 247,248, 7, + 25, 1, 1, 1, + 51, 1, 1, 1, + 51, 247,248,247, + 75, 247,248,247, + 75, 247,248, 7, + 100, 247,248, 7, + 102, 1, 1, 1, + 115, 1, 1, 1, + 115, 247,248, 7, + 138, 247,248, 7, + 139, 1, 1, 1, + 153, 1, 1, 1, + 153, 247,248, 7, + 179, 247,248, 7, + 181, 247,248,247, + 202, 247,248,247, + 203, 1, 1, 1, + 228, 1, 1, 1, + 229, 247,248, 7, + 249, 247,248, 7, + 255, 247,248, 7}; + +// Gradient palette "fib53_07_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-07.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( fib53_07_gp ) { + 0, 98,114,102, + 2, 98,114,240, + 24, 98,114,240, + 25, 239,114,240, + 50, 239,114,240, + 50, 98,241,240, + 75, 98,241,240, + 76, 239,114,102, + 101, 239,114,102, + 102, 239,241,240, + 118, 239,241,240, + 120, 1, 1, 1, + 134, 1, 1, 1, + 135, 239,241,240, + 151, 239,241,240, + 153, 239,114,102, + 177, 239,114,102, + 179, 98,241,240, + 203, 98,241,240, + 204, 239,114,240, + 228, 239,114,240, + 229, 98,114,240, + 252, 98,114,240, + 255, 98,114,102}; + +// Gradient palette "fib53_13_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-13.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 60 bytes of program space. + +DEFINE_PALETTE( fib53_13_gp ) { + 0, 6, 61,240, + 101, 6, 61,240, + 101, 239,241,240, + 127, 239,241,240, + 128, 1, 1, 1, + 152, 1, 1, 1, + 153, 239,241,240, + 178, 239,241,240, + 178, 6, 61,240, + 202, 6, 61,240, + 203, 239,241,240, + 229, 239,241,240, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "fib53_17_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-17.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 64 bytes of program space. + +DEFINE_PALETTE( fib53_17_gp ) { + 0, 227,231,140, + 2, 1, 1, 1, + 12, 1, 1, 1, + 13, 73,184, 31, + 76, 73,184, 31, + 77, 1, 1, 1, + 89, 1, 1, 1, + 89, 1,121, 1, + 166, 1,121, 1, + 166, 1, 1, 1, + 179, 1, 1, 1, + 179, 1, 56, 1, + 241, 1, 56, 1, + 241, 1, 1, 1, + 252, 1, 1, 1, + 255, 227,231,140}; + +// Gradient palette "fib53_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 72 bytes of program space. + +DEFINE_PALETTE( fib53_05_gp ) { + 0, 239,241,240, + 23, 239,241,240, + 25, 239,114,102, + 51, 239,114,102, + 51, 98,114,240, + 75, 98,114,240, + 77, 239,114,240, + 99, 239,114,240, + 101, 98,114,102, + 125, 98,114,102, + 127, 98,241,240, + 152, 98,241,240, + 153, 1, 1, 1, + 178, 1, 1, 1, + 179, 98,241,240, + 204, 98,241,240, + 205, 239,241,240, + 255, 239,241,240}; + +// Gradient palette "fib53_18_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-18.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( fib53_18_gp ) { + 0, 73,184, 31, + 179, 73,184, 31, + 179, 1, 1, 1, + 192, 1, 1, 1, + 193, 1, 56, 1, + 205, 1, 56, 1, + 205, 239,241,240, + 216, 239,241,240, + 217, 1,121, 1, + 229, 1,121, 1, + 230, 1, 1, 1, + 243, 1, 1, 1, + 243, 227,231,140, + 255, 227,231,140}; + +// Gradient palette "fib53_01_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/fib53/tn/fib53-01.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 80 bytes of program space. + +DEFINE_PALETTE( fib53_01_gp ) { + 0, 239,241,240, + 2, 6, 88, 77, + 30, 6, 88, 77, + 30, 239,241,240, + 45, 239,241,240, + 46, 73, 88, 77, + 61, 73, 88, 77, + 62, 239,241,240, + 77, 239,241,240, + 79, 6, 88, 77, + 173, 6, 88, 77, + 174, 239,241,240, + 191, 239,241,240, + 192, 73, 88, 77, + 209, 73, 88, 77, + 210, 239,241,240, + 224, 239,241,240, + 225, 6, 88, 77, + 252, 6, 88, 77, + 255, 239,241,240}; + +// Gradient palette "mccahon_16_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/mccahon/tn/mccahon-16.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( mccahon_16_gp ) { + 0, 237, 95, 29, + 61, 247,233,190, + 63, 109, 73, 1, + 125, 247,233,190, + 127, 186, 20, 5, + 190, 247,233,190, + 191, 3, 1, 1, + 255, 237, 95, 29}; + +// Gradient palette "frizzell_09_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-09.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 96 bytes of program space. + +DEFINE_PALETTE( frizzell_09_gp ) { + 0, 242,142, 10, + 1, 137, 19, 0, + 63, 137, 19, 0, + 65, 210,189,119, + 76, 210,189,119, + 78, 79, 25, 1, + 88, 79, 25, 1, + 89, 210,189,119, + 102, 210,189,119, + 103, 1, 5, 6, + 115, 1, 5, 6, + 115, 45, 68, 64, + 137, 45, 68, 64, + 139, 1, 5, 6, + 152, 1, 5, 6, + 153, 210,189,119, + 163, 210,189,119, + 165, 79, 25, 1, + 175, 79, 25, 1, + 178, 210,189,119, + 188, 210,189,119, + 191, 137, 19, 0, + 252, 137, 19, 0, + 255, 242,142, 10}; + +// Gradient palette "frizzell_10_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-10.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 56 bytes of program space. + +DEFINE_PALETTE( frizzell_10_gp ) { + 0, 45, 68, 64, + 11, 45, 68, 64, + 11, 242,142, 10, + 25, 242,142, 10, + 25, 1, 5, 6, + 38, 1, 5, 6, + 39, 210,189,119, + 49, 210,189,119, + 49, 79, 25, 1, + 63, 79, 25, 1, + 65, 210,189,119, + 76, 210,189,119, + 77, 137, 19, 0, + 255, 137, 19, 0}; + +// Gradient palette "frizzell_12_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-12.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 40 bytes of program space. -const byte candy2_gp[] PROGMEM = { - 0, 39, 33, 34, - 25, 4, 6, 15, - 48, 49, 29, 22, - 73, 224,173, 1, - 89, 177, 35, 5, - 130, 4, 6, 15, - 163, 255,114, 6, - 186, 224,173, 1, - 211, 39, 33, 34, - 255, 1, 1, 1}; +DEFINE_PALETTE( frizzell_12_gp ) { + 0, 45, 68, 64, + 2, 210,189,119, + 24, 210,189,119, + 25, 1, 5, 6, + 126, 1, 5, 6, + 126, 137, 19, 0, + 228, 137, 19, 0, + 230, 79, 25, 1, + 253, 79, 25, 1, + 255, 242,142, 10}; + +// Gradient palette "frizzell_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/frizzell/tn/frizzell-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 80 bytes of program space. + +DEFINE_PALETTE( frizzell_05_gp ) { + 0, 222,133, 47, + 1, 27, 17, 4, + 28, 27, 17, 4, + 28, 247,146,178, + 53, 247,146,178, + 55, 5, 1, 1, + 84, 5, 1, 1, + 84, 247,146,178, + 112, 247,146,178, + 113, 5, 1, 1, + 139, 5, 1, 1, + 140, 247,146,178, + 166, 247,146,178, + 168, 5, 1, 1, + 195, 5, 1, 1, + 196, 247,146,178, + 223, 247,146,178, + 224, 27, 17, 4, + 253, 27, 17, 4, + 255, 222,133, 47}; + +// Gradient palette "haiyan_23_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/haiyan/tn/haiyan-23.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 52 bytes of program space. + +DEFINE_PALETTE( haiyan_23_gp ) { + 0, 36,197,164, + 122, 36,197,164, + 124, 1, 1, 1, + 135, 1, 1, 1, + 136, 239,241,240, + 177, 239,241,240, + 178, 1, 1, 1, + 204, 1, 1, 1, + 205, 84,100, 88, + 229, 84,100, 88, + 230, 1, 1, 1, + 253, 1, 1, 1, + 255, 239,241,240}; + +// Gradient palette "janico_22_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/janico/tn/janico-22.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 108 bytes of program space. + +DEFINE_PALETTE( janico_22_gp ) { + 0, 112,109, 87, + 2, 126,109,115, + 37, 126,109,115, + 38, 83, 99,115, + 76, 83, 99,115, + 77, 148,127,115, + 89, 148,127,115, + 90, 112, 65, 73, + 127, 112, 65, 73, + 128, 148,127,115, + 141, 148,127,115, + 141, 4, 1, 1, + 151, 4, 1, 1, + 152, 112, 65, 53, + 165, 112, 65, 53, + 166, 4, 1, 1, + 178, 4, 1, 1, + 179, 69, 65, 53, + 202, 69, 65, 53, + 203, 4, 1, 1, + 214, 4, 1, 1, + 216, 194,225,255, + 230, 194,225,255, + 231, 4, 1, 1, + 252, 4, 1, 1, + 253, 4, 1, 1, + 255, 194,225,255}; + // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly // by name every time. const byte* const gGradientPalettes[] PROGMEM = { + ib_jul01_gp, //13-00 Jul01 + es_vintage_57_gp, + es_vintage_01_gp, + es_rivendell_15_gp, + rgi_15_gp, + retro2_16_gp, + Analogous_1_gp, + es_pinksplash_08_gp, + es_pinksplash_07_gp, + Coral_reef_gp, + + es_ocean_breeze_068_gp, + es_ocean_breeze_036_gp, + departure_gp, + es_landscape_64_gp, + es_landscape_33_gp, + rainbowsherbet_gp, + gr65_hult_gp, + gr64_hult_gp, + GMT_drywet_gp, + ib15_gp, + + Fuschia_7_gp, + es_emerald_dragon_08_gp, + lava_gp, + fire_gp, + haiyan_23_gp, + Colorfull_gp, + Magenta_Evening_gp, + Pink_Purple_gp, + Sunset_Real_gp, + es_autumn_19_gp, + + BlacK_Blue_Magenta_White_gp, + BlacK_Magenta_Red_gp, + BlacK_Red_Magenta_Yellow_gp, + Blue_Cyan_Yellow_gp, + Sunset_Yellow_gp, + cloud_gp, + fireandice_gp, + bhw2_39_gp, + rainfall_gp, + tashangel_gp, + + butterflytalker_gp, + os250k_metres_gp, + Night_Midnight_gp, + Afterdusk_gp, + BlueSky_gp, + Gold_Orange_gp, + frizzell_05_gp, + frizzell_09_gp, + frizzell_10_gp, + frizzell_12_gp, + + fib53_01_gp, + fib53_18_gp, + fib53_07_gp, + fib53_13_gp, + fib53_17_gp, + fib53_05_gp, + Analogous_02_gp, + Analogous_04a_gp, + Cyan_Orange_Stripped_gp, + Cyan_White_Green_gp, + + Wild_Orange_gp, + IKat_Radial_gp, + Citrus_gp, + Teal_Blue_gp, + Ldby_Orange_gp, + purple_orange_d07_gp, + blue_tan_d08_gp, + green_purple_d07_gp, + knoza_00_gp, + knoza_18_gp, + + calpan_18_gp, + calbayo_18_gp, + fib53_15_gp, + grindylow_15_gp, + grindylow_21_gp, + konjo_08_gp, + konjo_18_gp, + konjo_19_gp, + konkikyo_19_gp, + mccahon_16_gp, + sulz_10_gp, + sulz_12_gp, + sulz_15_gp, + sulz_21_gp, + sulz_22_gp, + Pills_2_gp, + Pink_Yellow_Orange_1_gp, + es_autumn_04_gp, + es_autumn_02_gp, + es_candide_30_gp, + es_chic_16_gp, + es_coffee_01_gp, + es_emerald_dragon_01_gp, + es_landscape_57_gp, + es_landscape_22_gp, + es_landscape_47_gp, + es_landscape_10_gp, + es_landscape_76_gp, + es_landscape_61_gp, + es_landscape_60_gp, + es_landscape_51_gp, + es_landscape_06_gp, + es_ocean_breeze_049_gp, + es_ocean_breeze_057_gp, + es_ocean_breeze_074_gp, + es_pinksplash_05_gp, + es_pinksplash_10_gp, + es_vintage_56_gp, + es_vintage_10_gp, + gold_yellow_gp, + radioactive_slime_gp, + pastel_rainbow_gp, + purple_sunset_gp, + janico_22_gp + +/* Sunset_Real_gp, //13-00 Sunset es_rivendell_15_gp, //14-01 Rivendell es_ocean_breeze_036_gp, //15-02 Breeze @@ -907,6 +2150,10 @@ const byte* const gGradientPalettes[] PROGMEM = { red_shift_gp, //68-55 Red Shift red_tide_gp, //69-56 Red Tide candy2_gp //70-57 Candy2 +*/ }; +const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); +#define GRADIENT_PALETTE_COUNT 114 + #endif diff --git a/wled00/wled.h b/wled00/wled.h index c4262dea3b..64829e5e7d 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -12,6 +12,10 @@ //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG +#define WLED_DISABLE_MQTT +#define WLED_DISABLE_LOXONE +#define WLED_DISABLE_ALEXA +#define WLED_DISABLE_INFRARED // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From 169e8e138c659fbcd93090e7cfa4160b02e6d759 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 5 Jul 2022 16:25:51 -0700 Subject: [PATCH 106/263] First version of externally-driven displays --- usermods/Tubes/Tubes.h | 66 +++++++++++ usermods/Tubes/beats.h | 72 ++++++++++++ usermods/Tubes/debug.h | 56 +++++++++ usermods/Tubes/options.h | 83 +++++++++++++ usermods/Tubes/pattern.h | 187 ++++++++++++++++++++++++++++++ usermods/Tubes/timer.h | 67 +++++++++++ usermods/Tubes/util.h | 16 +++ usermods/Tubes/virtual_strip.h | 206 +++++++++++++++++++++++++++++++++ wled00/FX.cpp | 20 +++- wled00/const.h | 1 + wled00/usermods_list.cpp | 9 ++ 11 files changed, 779 insertions(+), 4 deletions(-) create mode 100644 usermods/Tubes/Tubes.h create mode 100644 usermods/Tubes/beats.h create mode 100644 usermods/Tubes/debug.h create mode 100644 usermods/Tubes/options.h create mode 100644 usermods/Tubes/pattern.h create mode 100644 usermods/Tubes/timer.h create mode 100644 usermods/Tubes/util.h create mode 100644 usermods/Tubes/virtual_strip.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h new file mode 100644 index 0000000000..2243004f91 --- /dev/null +++ b/usermods/Tubes/Tubes.h @@ -0,0 +1,66 @@ +#pragma once + +#include "wled.h" + +#include "util.h" +#include "options.h" + +// #define MASTERCONTROL + +#define MASTER_PIN 6 +#define NUM_LEDS 64 + +#define USERADIO + +#include "FX.h" + +#include "beats.h" +#include "virtual_strip.h" + +// #include "controller.h" +// #include "radio.h" +// #include "debug.h" + + +class TubesUsermod : public Usermod { + private: + BeatController beats; + // Radio radio; + // PatternController controller(NUM_LEDS, &beats, &radio); + // DebugController debug(&controller); + + void randomize(long seed) { + for (int i = 0; i < seed % 16; i++) { + randomSeed(random(INT_MAX)); + } + random16_add_entropy( random(INT_MAX) ); + } + + public: + void setup() { + // Start timing + globalTimer.setup(); + beats.setup(); + // controller.setup(0); + // debug.setup(); + } + + void loop() + { + EVERY_N_MILLISECONDS(1000) { + randomize(random(INT_MAX)); + } + + beats.update(); // ~30us + // controller.update(); // radio: 0-3000us patterns: 0-3000us lcd: ~50000us + // debug.update(); // ~25us + + // Draw after everything else is done + // controller.led_strip->update(master != NULL); // ~25us + + CRGB *external_buffer = WS2812FX::get_external_buffer(); + for (int i = 0; i < 10; i++) { + external_buffer[i] = CRGB::White; + } + } +}; \ No newline at end of file diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h new file mode 100644 index 0000000000..67643f6eef --- /dev/null +++ b/usermods/Tubes/beats.h @@ -0,0 +1,72 @@ +#pragma once + +#include "timer.h" + +#define DEFAULT_BPM 120 + +typedef uint32_t BeatFrame_24_8; // 24:8 bitwise float + +// Regulates the beat counter, running patterns at 256 "fracs" per beat +class BeatController { + public: + accum88 bpm = 0; + BeatFrame_24_8 frac; + uint32_t accum = 0; + uint32_t micros_per_frac; + + void setup() + { + globalTimer.setup(); + + // Starts in phrase 1 + this->sync(DEFAULT_BPM << 8, 0); + } + + void update() + { + globalTimer.update(); + + // Maintains an accumulator with 14 bits of precision + this->accum += globalTimer.delta_micros << 8; + while (this->accum > this->micros_per_frac) { + this->frac++; + this->accum -= this->micros_per_frac; + } + } + + void sync(accum88 bpm, BeatFrame_24_8 frac) { + accum88 last_bpm = this->bpm; + this->bpm = bpm; + this->frac = frac; + this->accum = 0; + + this->micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); + + if (last_bpm != this->bpm) + this->print_bpm(); + } + + void set_bpm(accum88 bpm) { + this->sync(bpm, this->frac); + } + + void adjust_bpm(saccum78 bpm) { + this->sync(this->bpm + bpm, this->frac); + } + + void start_phrase() { + this->frac &= -0xFFF; + this->accum = 0; + } + + void print_bpm() { + Serial.print(this->bpm >> 8); + uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(F(".")); + if (frac < 10) + Serial.print(F("0")); + Serial.print(frac); + Serial.println(F("bpm")); + } + +}; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h new file mode 100644 index 0000000000..eb31bdbab3 --- /dev/null +++ b/usermods/Tubes/debug.h @@ -0,0 +1,56 @@ +#pragma once + + +class DebugController { + public: + PatternController *controller; + LEDs *strip; + Radio *radio; + uint32_t lastPhraseTime; + uint32_t lastFrame; + + DebugController(PatternController *controller) { + this->controller = controller; + this->strip = controller->led_strip; + this->radio = controller->radio; + } + + void setup() + { + this->lastPhraseTime = globalTimer.now_micros; + this->lastFrame = (uint32_t)-1; + } + + void update() + { + EVERY_N_MILLISECONDS( 10000 ) { + Serial.print(F("Free memory: ")); + Serial.println( freeMemory() ); + } + + // Show the beat on the master OR if debugging + + if (this->controller->options.debugging) { + uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; + this->strip->leds[p1] = CRGB::White; + + uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); + this->strip->leds[p2] = CRGB::White; + + uint8_t p3 = scale8(this->controller->radio->masterTubeId, this->strip->num_leds-1); + if (p3 == p2) { + this->strip->leds[p3] = CRGB::Green; + } else { + this->strip->leds[p3] = CRGB::Yellow; + } + if (this->radio->radioRestarts) { + this->strip->leds[1] = CRGB::Red; + } + } + + if (this->radio->radioFailures && !this->radio->radioRestarts) { + this->strip->leds[0] = CRGB::Red; + } + + } +}; diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h new file mode 100644 index 0000000000..33467b9869 --- /dev/null +++ b/usermods/Tubes/options.h @@ -0,0 +1,83 @@ +#pragma once + +typedef enum SyncMode { + All=0, + SinDrift=1, + Pulse=2, + Swing=3, + SwingDrift=4, +} SyncMode; + +typedef enum Duration: uint8_t { + ShortDuration=0, + MediumDuration=10, + LongDuration=20, + ExtraLongDuration=30, +} Duration; + +typedef enum Energy: uint8_t { + LowEnergy=0, + MediumEnergy=10, + HighEnergy=20, +} Energy; + +typedef struct ControlParameters { + public: + Duration duration=MediumDuration; + Energy energy=LowEnergy; + + ControlParameters(Duration duration=MediumDuration, Energy energy=LowEnergy) { + this->duration=duration; + this->energy=energy; + }; + +} ControlParams; + +typedef enum PenMode: uint8_t { + Draw=0, + Erase=1, + Blend=2, + Invert=3, + White=4, + Black=5, + Brighten=6, + Darken=7, + Flicker=8, +} PenMode; + +typedef enum EffectMode: uint8_t { + None=0, + Glitter=1, + Bubble=2, + Beatbox1=3, + Beatbox2=4, + Spark=5, + Flash=6, +} EffectMode; + +typedef enum BeatPulse: uint8_t { + Continuous=0, + Eighth=1, + Quarter=2, + Half=4, + Beat=8, + TwoBeats=16, + Measure=32, + TwoMeasures=64, + Phrase=128, +} BeatPulse; + +class EffectParameters { + public: + EffectMode effect; + PenMode pen=Draw; + BeatPulse beat=Beat; + uint8_t chance=255; + + EffectParameters(EffectMode effect=None, PenMode pen=Draw, BeatPulse beat=Beat, uint8_t chance=255) { + this->effect=effect; + this->pen=pen; + this->beat=beat; + this->chance=chance; + }; +}; diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h new file mode 100644 index 0000000000..77fdf883ed --- /dev/null +++ b/usermods/Tubes/pattern.h @@ -0,0 +1,187 @@ +#pragma once + +#include "virtual_strip.h" + +void rainbow(VirtualStrip *strip) +{ + // FastLED's built-in rainbow generator + fill_rainbow( strip->leds, strip->num_leds, strip->hue, 3); +} + +void palette_wave(VirtualStrip *strip) +{ + // FastLED's built-in rainbow generator + uint8_t hue = strip->hue; + for (uint8_t i=0; i < strip->num_leds; i++) { + CRGB c = strip->palette_color(i, hue); + nscale8x3(c.r, c.g, c.b, sin8(hue*8)); + strip->leds[i] = c; + hue++; + } +} + +void particleTest(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Black); + fill_solid( strip->leds, 2, strip->palette_color(0, strip->hue)); +} + +void solidBlack(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Black); +} + +void solidWhite(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::White); +} + +void solidRed(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Red); +} + +void solidBlue(VirtualStrip *strip) +{ + fill_solid( strip->leds, strip->num_leds, CRGB::Blue); +} + +void confetti(VirtualStrip *strip) +{ + strip->darken(2); + + int pos = random16(strip->num_leds); + strip->leds[pos] += strip->palette_color(random8(64), strip->hue); +} + +uint16_t random_offset = random16(); + +void biwave(VirtualStrip *strip) +{ + uint16_t l = strip->frame * 16; + l = sin16( l + random_offset ) + 32768; + + uint16_t r = strip->frame * 32; + r = cos16( r + random_offset ) + 32768; + + uint8_t p1 = scaled16to8(l, 0, strip->num_leds-1); + uint8_t p2 = scaled16to8(r, 0, strip->num_leds-1); + + if (p2 < p1) { + uint16_t t = p1; + p1 = p2; + p2 = t; + } + + strip->fill(CRGB::Black); + for (uint16_t p = p1; p <= p2; p++) { + strip->leds[p] = strip->palette_color(p*2, strip->hue*3); + } +} + +void sinelon(VirtualStrip *strip) +{ + // a colored dot sweeping back and forth, with fading trails + strip->darken(30); + + int pos = scale16(sin16( strip->frame << 5 ) + 32768, strip->num_leds-1); // beatsin16 re-implemented + strip->leds[pos] += strip->hue_color(); +} + +void bpm_palette(VirtualStrip *strip) +{ + uint8_t beat = strip->bpm_sin16(64, 255); + for (int i = 0; i < strip->num_leds; i++) { + CRGB c = strip->palette_color(i*2, strip->hue); + nscale8x3(c.r, c.g, c.b, beat-strip->hue+(i*10)); + strip->leds[i] = c; + } +} + +void bpm(VirtualStrip *strip) +{ + // colored stripes pulsing at a defined Beats-Per-Minute (BPM) + CRGBPalette16 palette = PartyColors_p; + + uint8_t beat = strip->bpm_sin16(64, 255); + for (int i = 0; i < strip->num_leds; i++) { + strip->leds[i] = ColorFromPalette(palette, strip->hue+(i*2), beat-strip->hue+(i*10)); + } +} + +void juggle(VirtualStrip *strip) +{ + // eight colored dots, weaving in and out of sync with each other + strip->darken(5); + + byte dothue = 0; + for( int i = 0; i < 8; i++) { + CRGB c = strip->palette_color(dothue + strip->hue); + // c = CHSV(dothue, 200, 255); + strip->leds[beatsin16( i+7, 0, strip->num_leds-1 )] |= c; + dothue += 32; + } +} + +uint8_t noise[MAX_VIRTUAL_LEDS]; + +void fillnoise8(uint32_t frame, uint8_t num_leds) { + uint16_t scale = 17; + uint8_t dataSmoothing = 240; + + for (int i = 0; i < num_leds; i++) { + uint8_t data = inoise8(i * scale, frame>>2); + + // The range of the inoise8 function is roughly 16-238. + // These two operations expand those values out to roughly 0..255 + data = qsub8(data,16); + data = qadd8(data,scale8(data,39)); + + uint8_t olddata = noise[i]; + uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); + noise[i] = newdata; + } +} + +void drawNoise(VirtualStrip *strip) +{ + // generate noise data + fillnoise8(strip->frame >> 2, strip->num_leds); + + for(int i = 0; i < strip->num_leds; i++) { + CRGB color = strip->palette_color(noise[i], strip->hue); + strip->leds[i] = color; + } +} + +typedef struct { + BackgroundFn backgroundFn; + ControlParameters control; +} PatternDef; + + +// List of patterns to cycle through. Each is defined as a separate function below. +PatternDef gPatterns[] = { + {drawNoise, {ShortDuration}}, + {drawNoise, {ShortDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {MediumDuration}}, + {drawNoise, {LongDuration}}, + {drawNoise, {LongDuration}}, + {rainbow, {ShortDuration}}, + {confetti, {ShortDuration}}, + {confetti, {MediumDuration}}, + + {juggle, {ShortDuration}}, + {bpm, {ShortDuration}}, + {bpm, {MediumDuration, HighEnergy}}, + {palette_wave, {ShortDuration}}, + {palette_wave, {MediumDuration}}, + {bpm_palette, {ShortDuration}}, + {bpm_palette, {MediumDuration, HighEnergy}} +}; + +/* +*/ +const uint8_t gPatternCount = ARRAY_SIZE(gPatterns); diff --git a/usermods/Tubes/timer.h b/usermods/Tubes/timer.h new file mode 100644 index 0000000000..22b00ab482 --- /dev/null +++ b/usermods/Tubes/timer.h @@ -0,0 +1,67 @@ +#pragma once + +class GlobalTimer { + public: + uint32_t now_millis; + uint32_t now_micros; + uint32_t last_micros; + uint32_t last_millis; + uint32_t delta_micros; + uint32_t delta_millis; + + void setup() + { + this->last_millis = this->now_millis = millis(); + this->last_micros = this->now_micros = micros(); + } + + void update() + { + this->last_millis = this->now_millis; + this->now_millis = millis(); + this->delta_millis = this->now_millis - this->last_millis; + + this->last_micros = this->now_micros; + this->now_micros = micros(); + this->delta_micros = this->now_micros - this->last_micros; + } +}; + +GlobalTimer globalTimer; + + + +class Timer { + public: + uint32_t markTime; + + void start(uint32_t duration_ms) { + this->markTime = globalTimer.now_millis + duration_ms; + } + + void stop() { + this->start(0); + } + + uint32_t since_mark() { + if (globalTimer.now_millis < this->markTime) + return 0; + return globalTimer.now_millis - this->markTime; + } + + void snooze(uint32_t duration_ms) { + while (this->markTime < globalTimer.now_millis) + this->markTime += duration_ms; + } + + bool ended() { + return globalTimer.now_millis > this->markTime; + } + + bool every(uint32_t duration_ms) { + if (!this->ended()) + return 0; + this->snooze(duration_ms); + return 1; + } +}; \ No newline at end of file diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h new file mode 100644 index 0000000000..25cbc7a163 --- /dev/null +++ b/usermods/Tubes/util.h @@ -0,0 +1,16 @@ +#pragma once + +#include "wled.h" + +uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16( v, rangewidth ); + uint16_t result = lowest + scaledbeat; + return result; +} + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +#define __ESP32__ +#define USTD_OPTION_FS_FORCE_NO_FS +#include \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h new file mode 100644 index 0000000000..177f64c14f --- /dev/null +++ b/usermods/Tubes/virtual_strip.h @@ -0,0 +1,206 @@ +#pragma once + +#include "util.h" +#include "options.h" +#include "beats.h" + +#define DEFAULT_FADE_SPEED 100 +#define MAX_VIRTUAL_LEDS 150 + +class VirtualStrip; +typedef void (*BackgroundFn)(VirtualStrip *strip); + +class Background { + public: + BackgroundFn animate; + CRGBPalette16 palette; + SyncMode sync=All; +}; + +typedef enum VirtualStripFade { + Steady=0, + FadeIn=1, + FadeOut=2, + Dead=99, +} VirtualStripFade; + +BeatFrame_24_8 swing(BeatFrame_24_8 frame) { + uint16_t fr = (frame & 0x3FF); // grab 4 beats + if (fr < 256) + fr = ease8InOutApprox(fr) << 2; + else + fr = 0x3FF; + + return (frame & 0xFC00) + fr; // recompose it +} + +class VirtualStrip { + const static uint16_t DEF_BRIGHT = 192; + + public: + CRGB leds[MAX_VIRTUAL_LEDS]; + uint8_t num_leds; + uint8_t brightness; + + // Fade in/out + VirtualStripFade fade; + uint16_t fader; + uint8_t fade_speed; + + // Pattern parameters + Background background; + uint32_t frame; + uint8_t beat; + uint16_t beat16; // 8 bits of beat and 8 bits of fractional + uint8_t hue; + bool beat_pulse; + int bps = 0; + + VirtualStrip(uint8_t num_leds) + { + this->fade = Dead; + this->num_leds = num_leds; + } + + void load(Background &background, uint8_t fade_speed=DEFAULT_FADE_SPEED) + { + this->background = background; + this->fade = FadeIn; + this->fader = 0; + this->fade_speed = fade_speed; + this->brightness = DEF_BRIGHT; + } + + void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) + { + if (this->fade == Dead) + return; + this->fade = FadeOut; + this->fade_speed = fade_speed; + } + + void darken(uint8_t amount=10) + { + fadeToBlackBy( this->leds, this->num_leds, amount); + } + + void fill(CRGB crgb) + { + fill_solid( this->leds, this->num_leds, crgb); + } + + void update(BeatFrame_24_8 frame, uint8_t beat_pulse) + { + if (this->fade == Dead) + return; + + this->frame = frame; + + switch (this->background.sync) { + case All: + break; + + case SinDrift: + // Drift slightly + this->frame = frame + (beatsin16( 5 ) >> 6); + break; + + case Swing: + // Swing the beat + this->frame = swing(frame); + break; + + case SwingDrift: + // Swing the beat AND drift slightly + this->frame = swing(frame) + (beatsin16( 5 ) >> 6); + break; + + case Pulse: + // Pulsing from 30 - 210 brightness + this->brightness = scale8(beatsin8( 10 ), 180) + 30; + break; + } + this->hue = (this->frame >> 4) % 256; + this->beat = (this->frame >> 8) % 16; + this->beat_pulse = beat_pulse; + + // Animate this virtual strip + this->background.animate(this); + + switch (this->fade) { + case Steady: + case Dead: + break; + + case FadeIn: + if (65535 - this->fader < this->fade_speed) { + this->fader = 65535; + this->fade = Steady; + } else { + this->fader += this->fade_speed; + } + break; + + case FadeOut: + if (this->fader < this->fade_speed) { + this->fader = 0; + this->fade = Dead; + return; + } else { + this->fader -= this->fade_speed; + } + break; + } + } + + CRGB palette_color(uint8_t c, uint8_t offset=0) { + return ColorFromPalette( this->background.palette, c + offset ); + } + + CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { + return CHSV(this->hue + offset, saturation, value); + } + + void blend(CRGB strip[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { + if (this->fade == Dead) + return; + + brightness = scale8(this->brightness, brightness); + + for (unsigned i=0; i < num_leds; i++) { +#ifdef DOUBLED + uint8_t pos = (2*i + 1) % this->num_leds; // slope of line is fixed right now at 2:1 +#else + uint8_t pos = i; +#endif + + CRGB c = this->leds[pos]; + +#ifdef DOUBLED + CRGB c1 = this->leds[pos-1]; + CRGB c2 = this->leds[pos+1]; + nblend(c1, c, 128); + nblend(c, c2, 128); + nblend(c, c1, 128); // C is now a weighted average of the three virtual pixels +#endif + + nscale8x3(c.r, c.g, c.b, brightness); + nscale8x3(c.r, c.g, c.b, this->fader>>8); + if (overwrite) + strip[i] = c; + else + strip[i] |= c; + } + } + + uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) + { + return scaled16to8(sin16( this->frame << 7 ) + 32768, lowest, highest); + } + + uint8_t bpm_cos16( uint16_t lowest=0, uint16_t highest=65535 ) + { + return scaled16to8(cos16( this->frame << 7 ) + 32768, lowest, highest); + } + +}; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 92885ad399..189e3bd13c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4504,7 +4504,6 @@ uint16_t mode_aurora(void) { return FRAMETIME; } -<<<<<<< HEAD static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;sx=24,pal=50,1d"; // WLED-SR effects @@ -5285,6 +5284,20 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;!,!,!;!;2d"; +uint16_t WS2812FX::mode_external(void) { + // uint8_t segment_id = strip.getMainSegmentId(); + uint16_t length = strip.getLengthTotal(); + + for (int i = 0, p = 0; i < length; i++, p++) { + if (p >= EXTERNAL_BUFFER_SIZE) { + p = 0; + } + // strip.setPixelColor(i, color_from_palette(external_buffer[p], true, PALETTE_SOLID_WRAP, 0)); + strip.setPixelColor(i, external_buffer[p]); + } +} +static const char _data_FX_MODE_TUBES_NOISE[] PROGMEM = "External!@!,;!,!,;!;1d"; + //////////////////////////////// // 2D Polar Lights // //////////////////////////////// @@ -7452,7 +7465,8 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE); addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); - addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); + // addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); + addEffect(FX_MODE_EXTERNAL, &mode_external, _data_FX_MODE_EXTERNAL); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); // --- 2D non-audio effects --- @@ -7518,7 +7532,6 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); } -======= @@ -7562,4 +7575,3 @@ uint16_t WS2812FX::mode_tubes_moise(void) { return FRAMETIME; } ->>>>>>> 3b8e5a2f (Initial port of Tubes - palettes and noise functions) diff --git a/wled00/const.h b/wled00/const.h index 95b471cb1a..3ab56fbdea 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -84,6 +84,7 @@ #define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h" #define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h #define USERMOD_ID_AUDIOREACTIVE 31 //Usermod "audioreactive.h" +#define USERMOD_ID_TUBES 32 //Usermod "usermod_tubes.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index d63730d45b..0c37efe320 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -136,6 +136,10 @@ #include "../usermods/audioreactive/audio_reactive.h" #endif +#ifdef USERMOD_TUBES +#include "../usermods/Tubes/Tubes.h" +#endif + void registerUsermods() { /* @@ -259,4 +263,9 @@ void registerUsermods() #ifdef USERMOD_AUDIOREACTIVE usermods.add(new AudioReactive()); #endif + + #ifdef USERMOD_TUBES + usermods.add(new TubesUsermod()); + #endif + } From 628c786c2bd9fab27035d36a8cb4ed9a65606048 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 6 Jul 2022 00:03:15 -0700 Subject: [PATCH 107/263] Light tubes are up and running --- usermods/Tubes/Tubes.h | 72 +++- usermods/Tubes/controller.h | 686 +++++++++++++++++++++++++++++++++ usermods/Tubes/debug.h | 4 +- usermods/Tubes/effects.h | 155 ++++++++ usermods/Tubes/global_state.h | 54 +++ usermods/Tubes/led_strip.h | 60 +++ usermods/Tubes/particle.h | 227 +++++++++++ usermods/Tubes/radio.h | 252 ++++++++++++ usermods/Tubes/virtual_strip.h | 13 - 9 files changed, 1488 insertions(+), 35 deletions(-) create mode 100644 usermods/Tubes/controller.h create mode 100644 usermods/Tubes/effects.h create mode 100644 usermods/Tubes/global_state.h create mode 100644 usermods/Tubes/led_strip.h create mode 100644 usermods/Tubes/particle.h create mode 100644 usermods/Tubes/radio.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 2243004f91..cb167cedd0 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -7,27 +7,28 @@ // #define MASTERCONTROL -#define MASTER_PIN 6 -#define NUM_LEDS 64 +#define MASTER_PIN 6 -#define USERADIO +// #define USERADIO #include "FX.h" #include "beats.h" #include "virtual_strip.h" +#include "led_strip.h" -// #include "controller.h" -// #include "radio.h" -// #include "debug.h" +#include "controller.h" +#include "radio.h" +#include "debug.h" class TubesUsermod : public Usermod { private: BeatController beats; - // Radio radio; - // PatternController controller(NUM_LEDS, &beats, &radio); - // DebugController debug(&controller); + Radio radio; + PatternController controller = PatternController(MAX_REAL_LEDS, &beats, &radio); + DebugController debug = DebugController(&controller); + int* master = NULL; /* master.h deleted */ void randomize(long seed) { for (int i = 0; i < seed % 16; i++) { @@ -41,8 +42,8 @@ class TubesUsermod : public Usermod { // Start timing globalTimer.setup(); beats.setup(); - // controller.setup(0); - // debug.setup(); + controller.setup(0); + debug.setup(); } void loop() @@ -51,16 +52,45 @@ class TubesUsermod : public Usermod { randomize(random(INT_MAX)); } - beats.update(); // ~30us - // controller.update(); // radio: 0-3000us patterns: 0-3000us lcd: ~50000us - // debug.update(); // ~25us + beats.update(); + controller.update(); + debug.update(); // Draw after everything else is done - // controller.led_strip->update(master != NULL); // ~25us - - CRGB *external_buffer = WS2812FX::get_external_buffer(); - for (int i = 0; i < 10; i++) { - external_buffer[i] = CRGB::White; - } + controller.led_strip->update(master != NULL); // ~25us } -}; \ No newline at end of file +}; + + + + +/* +LIST OF GOOD PATTERNS + +Aurora +Dynamic Smooth +Blends +Colortwinkles +Fillnoise - maybe +Fireworks +Fireworks Starburst +Flow +Gradient - maybe +Juggle - maybe +Lake +Meteor Smooth - maybe +Noise 2 +Noise 4 +Pacifica +Palette - maybe +Phased - maybe +Plasma +Ripple +Running Dual +Saw - maybe +Sinelon Dual - maybe +Tetrix - maybe +Twinklecat +Twinkleup + +*/ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h new file mode 100644 index 0000000000..01539dc63c --- /dev/null +++ b/usermods/Tubes/controller.h @@ -0,0 +1,686 @@ +#pragma once + +#include "beats.h" + +#include "pattern.h" +#include "palettes.h" +#include "effects.h" +#include "global_state.h" +#include "radio.h" + +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 144; + +const static CommandId COMMAND_UPDATE = 0x411; +const static CommandId COMMAND_NEXT = 0x321; +const static CommandId COMMAND_RESET = 0x911; +const static CommandId COMMAND_FIREWORK = 0xFFF; +const static CommandId COMMAND_HELLO = 0x000; +const static CommandId COMMAND_OPTIONS = 0x123; +const static CommandId COMMAND_BRIGHTNESS = 0x888; + + +typedef struct { + bool debugging; + uint8_t brightness; +} ControllerOptions; + +#define NEXT_PATTERN_TIME 53000 +#define NEXT_PALETTE_TIME 27000 + +#define NUM_VSTRIPS 3 + +#define DEBOUNCE_TIME 40 + +class Button { + public: + Timer debounceTimer; + uint8_t pin; + bool lastPressed = false; + + void setup(uint8_t pin) { + this->pin = pin; + pinMode(pin, INPUT_PULLUP); + this->debounceTimer.start(0); + } + + bool pressed() { + if (digitalRead(this->pin) == HIGH) { + return !this->debounceTimer.ended(); + } + + this->debounceTimer.start(DEBOUNCE_TIME); + return true; + } + + bool triggered() { + // Triggers BOTH low->high AND high->low + bool p = this->pressed(); + bool lp = this->lastPressed; + this->lastPressed = p; + return p != lp; + } +}; + +class PatternController : public MessageReceiver { + public: + const static int FRAMES_PER_SECOND = 300; // how often we animate, in frames per second + const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds + + uint8_t num_leds; + VirtualStrip *vstrips[NUM_VSTRIPS]; + uint8_t next_vstrip = 0; + bool isMaster = false; + + Timer graphicsTimer; + Timer updateTimer; + Timer slaveTimer; + +#ifdef USELCD + Lcd *lcd; +#endif + LEDs *led_strip; + BeatController *beats; + Radio *radio; + Effects *effects; + + ControllerOptions options; + char key_buffer[20] = {0}; + + Energy energy=LowEnergy; + TubeState current_state; + TubeState next_state; + + PatternController(uint8_t num_leds, BeatController *beats, Radio *radio) { + this->num_leds = num_leds; +#ifdef USELCD + this->lcd = new Lcd(); +#endif + this->led_strip = new LEDs(num_leds); + this->beats = beats; + this->radio = radio; + this->effects = new Effects(); + + for (uint8_t i=0; i < NUM_VSTRIPS; i++) { +#ifdef DOUBLED + this->vstrips[i] = new VirtualStrip(num_leds * 2 + 1); +#else + this->vstrips[i] = new VirtualStrip(num_leds); +#endif + } + + } + + void setup(bool isMaster) + { + this->isMaster = isMaster; + this->options.debugging = false; + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + +#ifdef USELCD + this->lcd->setup(); +#endif + this->set_next_pattern(0); + this->set_next_palette(0); + this->set_next_effect(0); + this->next_state.pattern_phrase = 0; + this->next_state.palette_phrase = 0; + this->next_state.effect_phrase = 0; + Serial.println(F("Patterns: ok")); + + this->radio->setup(this->isMaster); + this->radio->sendCommand(COMMAND_HELLO); + + this->slaveTimer.start(RADIO_SENDPERIOD * 3); // Assume we're a slave at first, just listen for a master. + this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to + } + + void update() + { + this->read_keys(); + + // If master has expired, clear masterId + if (this->radio->masterTubeId && this->slaveTimer.ended()) { + Serial.println(F("I have no master")); + this->radio->masterTubeId = 0; + } + + // Update patterns to the beat + this->update_beat(); + + uint16_t phrase = this->current_state.beat_frame >> 12; + if (phrase >= this->next_state.pattern_phrase) { + this->load_pattern(this->next_state); + this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + } + if (phrase >= this->next_state.palette_phrase) { + this->load_palette(this->next_state); + this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + } + if (phrase >= this->next_state.effect_phrase) { + this->load_effect(this->next_state); + this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + } + + // If alone or master, send out updates + if (!this->radio->masterTubeId and this->updateTimer.ended()) { + this->send_update(); + } + + this->radio->receiveCommands(this); + + if (this->graphicsTimer.every(REFRESH_PERIOD)) { + this->updateGraphics(); + } + +#ifdef USELCD + if (this->lcd->active) { + this->lcd->size(1); + this->lcd->write(0,56, this->current_state.beat_frame); + this->lcd->write(80,56, this->x_axis); + this->lcd->write(100,56, this->y_axis); + this->lcd->show(); + + this->lcd->update(); + } +#endif + } + + void restart_phrase() { + this->beats->start_phrase(); + this->update_beat(); + this->send_update(); + } + + void set_phrase_position(uint8_t pos) { + this->beats->sync(this->beats->bpm, (this->beats->frac & -0xFFF) + (pos<<8)); + this->update_beat(); + this->send_update(); + } + + void set_tapped_bpm(accum88 bpm, uint8_t pos=15) { + // By default, restarts at 15th beat - because this is the end of a tap + this->beats->sync(bpm, (this->beats->frac & -0xFFF) + (pos<<8)); + this->update_beat(); + this->send_update(); + } + + void update_beat() { + this->current_state.bpm = this->next_state.bpm = this->beats->bpm; + this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) + if (this->current_state.bpm >= 125>>8) + this->energy = HighEnergy; + else if (this->current_state.bpm > 120>>8) + this->energy = MediumEnergy; + else + this->energy = LowEnergy; + } + + void send_update() { + this->current_state.print(); + Serial.print(F(" ")); + + if (this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state))) { + this->radio->radioFailures = 0; + this->updateTimer.snooze(RADIO_SENDPERIOD); + } else { + // might have been a collision. Back off by a small amount determined by ID + Serial.println(F("Radio update failed")); + this->updateTimer.snooze( this->radio->tubeId & 0x7F ); + this->radio->radioFailures++; + if (this->radio->radioFailures > 100) { + this->radio->setup(this->isMaster); + this->radio->radioRestarts++; + } + } + + uint16_t phrase = this->current_state.beat_frame >> 12; + Serial.print(F(" ")); + Serial.print(this->next_state.pattern_phrase - phrase); + Serial.print(F("P ")); + Serial.print(this->next_state.palette_phrase - phrase); + Serial.print(F("C ")); + Serial.print(this->next_state.effect_phrase - phrase); + Serial.print(F("E: ")); + this->next_state.print(); + Serial.print(F(" ")); + this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + Serial.println(); + } + + void background_changed() { + this->update_background(); + this->current_state.print(); + Serial.println(); + } + + void load_pattern(TubeState &tube_state) { + if (this->current_state.pattern_id == tube_state.pattern_id + && this->current_state.pattern_sync_id == tube_state.pattern_sync_id) + return; + + this->current_state.pattern_phrase = tube_state.pattern_phrase; + this->current_state.pattern_id = tube_state.pattern_id % gPatternCount; + this->current_state.pattern_sync_id = tube_state.pattern_sync_id; + + Serial.print(F("Change pattern ")); + this->background_changed(); + } + + uint16_t set_next_pattern(uint16_t phrase) { + uint8_t pattern_id = random8(gPatternCount); + PatternDef def = gPatterns[pattern_id]; + if (def.control.energy > this->energy) { + pattern_id = 0; + def = gPatterns[0]; + } + + this->next_state.pattern_id = pattern_id; + this->next_state.pattern_sync_id = this->randomSyncMode(); + + switch (def.control.duration) { + case ShortDuration: return random8(5,15); + case MediumDuration: return random8(15,25); + case LongDuration: return random8(35,45); + case ExtraLongDuration: return random8(70, 100); + } + return 5; + } + + void load_palette(TubeState &tube_state) { + if (this->current_state.palette_id == tube_state.palette_id) + return; + + this->current_state.palette_phrase = tube_state.palette_phrase; + this->_load_palette(tube_state.palette_id); + } + + void _load_palette(uint8_t palette_id) { + this->current_state.palette_id = palette_id % gGradientPaletteCount; + + Serial.print(F("Change palette")); + this->background_changed(); + } + + uint16_t set_next_palette(uint16_t phrase) { + this->next_state.palette_id = random8(gGradientPaletteCount); + return random8(4,40); + } + + void load_effect(TubeState &tube_state) { + if (this->current_state.effect_params.effect == tube_state.effect_params.effect && + this->current_state.effect_params.pen == tube_state.effect_params.pen && + this->current_state.effect_params.chance == tube_state.effect_params.chance) + return; + + this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; + this->_load_effect(tube_state.effect_params); + } + + void _load_effect(EffectParameters params) { + this->current_state.effect_params = params; + + Serial.print(F("Change effect ")); + this->current_state.print(); + Serial.println(); + + this->effects->load(this->current_state.effect_params); + } + + uint16_t set_next_effect(uint16_t phrase) { + EffectDef def = gEffects[random8(gEffectCount)]; + if (def.control.energy > this->energy) + def = gEffects[0]; + + this->next_state.effect_params = def.params; + + switch (def.control.duration) { + case ShortDuration: return 3; + case MediumDuration: return 6; + case LongDuration: return 10; + case ExtraLongDuration: return 20; + } + return 1; + } + + void update_background() { + Background background; + background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; + background.palette = gGradientPalettes[this->current_state.palette_id]; + background.sync = (SyncMode)this->current_state.pattern_sync_id; + + // re-use virtual strips to prevent heap fragmentation + for (uint8_t i = 0; i < NUM_VSTRIPS; i++) { + this->vstrips[i]->fadeOut(); + } + this->vstrips[this->next_vstrip]->load(background); + this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; + } + + void optionsChanged() { + if (this->isMaster) { + this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); + } + } + + void setBrightness(uint8_t brightness) { + Serial.print(F("brightness ")); + Serial.println(brightness); + + this->options.brightness = brightness; + this->optionsChanged(); + } + + void setDebugging(bool debugging) { + Serial.print(F("debugging ")); + Serial.println(debugging); + + this->options.debugging = debugging; + this->optionsChanged(); + } + + SyncMode randomSyncMode() { + uint8_t r = random8(128); + if (r < 40) + return SinDrift; + if (r < 65) + return Pulse; + if (r < 72) + return Swing; + if (r < 84) + return SwingDrift; + return All; + } + + void updateGraphics() { + static BeatFrame_24_8 lastFrame = 0; + BeatFrame_24_8 beat_frame = this->current_state.beat_frame; + + uint8_t beat_pulse = 0; + for (int i = 0; i < 8; i++) { + if ( (beat_frame >> (5+i)) != (lastFrame >> (5+i))) + beat_pulse |= 1<vstrips[i]; + if (vstrip->fade == Dead) + continue; + + // Remember the first strip + if (first_strip == NULL) + first_strip = vstrip; + + vstrip->update(beat_frame, beat_pulse); + vstrip->blend(this->led_strip->leds, this->led_strip->num_leds, this->options.brightness, vstrip == first_strip); + } + + this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); + this->effects->draw(this->led_strip->leds, this->num_leds); + } + + virtual void acknowledge() { + addFlash(); + } + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + if (fromId) { + Serial.print(F("From ")); + Serial.print(fromId); + Serial.print(F(": ")); + } + + switch (command) { + case COMMAND_FIREWORK: + Serial.print(F("fireworks")); + this->acknowledge(); + return; + + case COMMAND_RESET: + Serial.print(F("reset")); + return; + + case COMMAND_BRIGHTNESS: { + uint8_t *bright = (uint8_t *)data; + this->setBrightness(*bright); + return; + } + + case COMMAND_HELLO: + Serial.print(F("hello")); + this->updateTimer.stop(); + return; + + case COMMAND_OPTIONS: { + Serial.print(F("options")); + memcpy(&this->options, data, sizeof(this->options)); + return; + } + + case COMMAND_NEXT: { + Serial.print(F(" next ")); + if (fromId < this->radio->masterTubeId) { + Serial.print(F(" (ignoring)")); + return; + } + + memcpy(&this->next_state, data, sizeof(TubeState)); + this->next_state.print(); + Serial.print(F(" (obeying)")); + return; + } + + case COMMAND_UPDATE: { + Serial.print(F(" update ")); + if (fromId < this->radio->masterTubeId) { + Serial.print(F(" (ignoring)")); + return; + } + + TubeState state; + memcpy(&state, data, sizeof(TubeState)); + state.print(); + Serial.print(F(" (obeying)")); + + // Track the last time we received a message from our master + this->slaveTimer.start(RADIO_SENDPERIOD * 8); + + // Catch up to this state + this->load_pattern(state); + this->load_palette(state); + this->load_effect(state); + this->beats->sync(state.bpm, state.beat_frame); + return; + } + } + + Serial.print(F("UNKNOWN ")); + Serial.print(command, HEX); + } + + void read_keys() { + if (!Serial.available()) + return; + + char c = Serial.read(); + char *k = this->key_buffer; + uint8_t max = sizeof(this->key_buffer); + for (uint8_t i=0; *k && (i < max-1); i++) { + k++; + } + if (c == 10) { + this->keyboard_command(this->key_buffer); + this->key_buffer[0] = 0; + } else { + *k++ = c; + *k = 0; + } + } + + accum88 parse_number(char *s) { + uint16_t n=0, d=0; + + while (*s == ' ') + s++; + while (*s) { + if (*s < '0' || *s > '9') + break; + n = n*10 + (*s++ - '0'); + } + n = n << 8; + + if (*s == '.') { + uint16_t div = 1; + s++; + while (*s) { + if (*s < '0' || *s > '9') + break; + d = d*10 + (*s++ - '0'); + div *= 10; + } + d = (d << 8) / div; + } + return n+d; + } + + void keyboard_command(char *command) { + uint8_t b; + accum88 arg = this->parse_number(command+1); + + switch (command[0]) { + case 'f': + this->radio->sendCommandFrom(255, COMMAND_FIREWORK, NULL, 0); + this->onCommand(0, COMMAND_FIREWORK, NULL); + Serial.println(); + break; + + case 'i': + this->radio->resetId(arg >> 8); + break; + + case 'd': + this->setDebugging(!this->options.debugging); + break; + + case '-': + b = this->options.brightness; + while (*command++ == '-') + b -= 5; + this->setBrightness(b - 5); + break; + case '+': + b = this->options.brightness; + while (*command++ == '+') + b += 5; + this->setBrightness(b + 5); + return; + case 'l': + if (arg < 5*256) { + Serial.println(F("nope")); + return; + } + this->setBrightness(arg >> 8); + return; + + case 'b': + if (arg < 60*256) { + Serial.println(F("nope")); + return; + } + this->beats->set_bpm(arg); + this->update_beat(); + this->send_update(); + return; + + case 's': + this->beats->start_phrase(); + this->update_beat(); + this->send_update(); + return; + + case 'n': + this->force_next(); + return; + + case 'p': + this->next_state.pattern_phrase = 0; + this->next_state.pattern_id = arg >> 8; + this->next_state.pattern_sync_id = All; + this->update_next(); + return; + + case 'm': + this->next_state.pattern_phrase = 0; + this->next_state.pattern_id = this->current_state.pattern_id; + this->next_state.pattern_sync_id = arg >> 8; + this->update_next(); + return; + + case 'c': + this->next_state.palette_phrase = 0; + this->next_state.palette_id = arg >> 8; + this->update_next(); + return; + + case 'e': + this->next_state.effect_phrase = 0; + this->next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; + this->update_next(); + return; + + case '%': + this->next_state.effect_phrase = 0; + this->next_state.effect_params = this->current_state.effect_params; + this->next_state.effect_params.chance = arg; + this->update_next(); + return; + + case 'h': + // Pretend to receive a HELLO + this->onCommand(0, COMMAND_HELLO, NULL); + Serial.println(); + return; + + case 'g': + /* PARTICLES + for (int i=0; i< 10; i++) + addGlitter(); + */ + break; + + case '?': + Serial.println(F("b###.# - set bpm")); + Serial.println(F("s - start phrase")); + Serial.println(); + Serial.println(F("p### - patterns")); + Serial.println(F("m### - sync mode")); + Serial.println(F("c### - colors")); + Serial.println(F("e### - effects")); + Serial.println(); + Serial.println(F("i### - set ID")); + Serial.println(F("d - toggle debugging")); + Serial.println(F("l### - brightness")); + } + } + + void force_next() { + uint16_t phrase = this->current_state.beat_frame >> 12; + uint16_t next_phrase = min(this->next_state.pattern_phrase, min(this->next_state.palette_phrase, this->next_state.effect_phrase)) - phrase; + this->next_state.pattern_phrase -= next_phrase; + this->next_state.palette_phrase -= next_phrase; + this->next_state.effect_phrase -= next_phrase; + this->update_next(); + } + + void update_next() { + this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + } + +}; + + + +// What's interesting? +// c53 - clouds +// m4 - swing drift diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index eb31bdbab3..8f4717ab89 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,5 +1,8 @@ #pragma once +#include "controller.h" +#include "radio.h" + class DebugController { public: @@ -51,6 +54,5 @@ class DebugController { if (this->radio->radioFailures && !this->radio->radioRestarts) { this->strip->leds[0] = CRGB::Red; } - } }; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h new file mode 100644 index 0000000000..f4ad7532c2 --- /dev/null +++ b/usermods/Tubes/effects.h @@ -0,0 +1,155 @@ +#pragma once + +#include "util.h" +#include "particle.h" +#include "virtual_strip.h" + +void addGlitter(CRGB color=CRGB::White, PenMode pen=Draw) +{ + addParticle(new Particle(random16(), color, pen, 128)); +} + +void addSpark(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 64); + uint8_t r = random8(); + if (r > 128) + particle->velocity = r; + else + particle->velocity = -(128 + r); + addParticle(particle); +} + +void addBeatbox(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 256, drawBeatbox); + addParticle(particle); +} + +void addBubble(CRGB color=CRGB::White, PenMode pen=Draw) +{ + Particle *particle = new Particle(random16(), color, pen, 1024, drawPop); + particle->velocity = random16(0, 40) - 20; + addParticle(particle); +} + +void addFlash(CRGB color=CRGB::Blue, PenMode pen=Draw) +{ + addParticle(new Particle(random16(), color, pen, 256, drawFlash)); +} + +void addDrop(CRGB color, PenMode pen=Draw) +{ + Particle *particle = new Particle(65535, color, pen, 360); + particle->velocity = -500; + particle->gravity = -10; + addParticle(particle); +} + +class Effects { + public: + EffectMode effect=None; + PenMode pen=Draw; + BeatPulse beat; + uint8_t chance; + + void load(EffectParameters ¶ms) { + this->effect = params.effect; + this->pen = params.pen; + this->beat = params.beat; + this->chance = params.chance; + } + + void update(VirtualStrip *strip, BeatFrame_24_8 beat_frame, BeatPulse beat_pulse) { + if (!this->beat || beat_pulse & this->beat) { + + if (random8() <= this->chance) { + CRGB color = strip->palette_color(random8()); + + switch (this->effect) { + case None: + break; + + case Glitter: + addGlitter(color, this->pen); + break; + + case Beatbox1: + case Beatbox2: + addBeatbox(color, this->pen); + if (this->effect == Beatbox2) + addBeatbox(color, this->pen); + break; + + case Bubble: + addBubble(color, this->pen); + break; + + case Spark: + addSpark(color, this->pen); + break; + + case Flash: + addFlash(CRGB::White, this->pen); + break; + } + } + } + + this->animate(beat_frame, beat_pulse); + } + + void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { + unsigned int len = 0; /* PARTICLES particles.length(); */ + for (unsigned i=len; i > 0; i--) { + Particle *particle = particles[i-1]; + + particle->update(frame); + if (particle->age > particle->lifetime) { + delete particle; + /* PARTICLES particles.erase(i-1); */ + continue; + } + } + } + + void draw(CRGB strip[], uint8_t num_leds) { + uint8_t len = 0; /* PARTICLES particles.length(); */ + for (uint8_t i=0; idrawFn(particle, strip, num_leds); + } + } + +}; + + +typedef struct { + EffectParameters params; + ControlParameters control; +} EffectDef; + + +static const EffectDef gEffects[] = { + {{None}, {LongDuration}}, + {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, + {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, + {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, + {{Flash, Brighten, Phrase}, {MediumDuration, HighEnergy}}, + {{Flash, Darken, Measure}, {ShortDuration, LowEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Glitter, Brighten, Eighth, 80}, {MediumDuration, MediumEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {MediumDuration, HighEnergy}}, + {{Glitter, Darken, Eighth, 40}, {MediumDuration, LowEnergy}}, + + {{Glitter, Draw, Eighth, 10}, {LongDuration, LowEnergy}}, + {{Glitter, Draw, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Glitter, Invert, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Beatbox2, Black}, {MediumDuration, LowEnergy}}, + {{Beatbox2, Draw}, {ShortDuration, HighEnergy}}, + {{Bubble, Darken}, {MediumDuration, LowEnergy}}, + {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, + {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, +}; +const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h new file mode 100644 index 0000000000..a23a769aae --- /dev/null +++ b/usermods/Tubes/global_state.h @@ -0,0 +1,54 @@ +#pragma once + +#include "wled.h" +#include "beats.h" +#include "effects.h" + + +class TubeState { + public: + // Global clock: frames are defined as 1/64th of a beat + accum88 bpm = 0; // BPM in high 8 bits, fraction in low 8 bits + BeatFrame_24_8 beat_frame = 0; // current beat (24 bits) and fractional beat (8bits) + + uint16_t pattern_phrase; + uint8_t pattern_id; + uint8_t pattern_sync_id; + + uint16_t palette_phrase; + uint8_t palette_id; + + uint16_t effect_phrase; + EffectParameters effect_params; + + void print() { + uint16_t phrase = this->beat_frame >> 12; + Serial.print(F("[")); + Serial.print(phrase); + Serial.print(F(".")); + Serial.print((this->beat_frame >> 8) % 16); + Serial.print(F(" P")); + Serial.print(this->pattern_id); + Serial.print(F(",")); + Serial.print(this->pattern_sync_id); + Serial.print(F(" C")); + Serial.print(this->palette_id); + Serial.print(F(" E")); + Serial.print(this->effect_params.effect); + Serial.print(F(",")); + Serial.print(this->effect_params.pen); + Serial.print(F(",")); + Serial.print(this->effect_params.beat); + Serial.print(F(",")); + Serial.print(this->effect_params.chance); + Serial.print(F(" ")); + Serial.print(this->bpm >> 8); + uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(F(".")); + if (frac < 10) + Serial.print(F("0")); + Serial.print(frac); + Serial.print(F("bpm]")); + } + +}; diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h new file mode 100644 index 0000000000..ddbca2fd4b --- /dev/null +++ b/usermods/Tubes/led_strip.h @@ -0,0 +1,60 @@ +#pragma once + +#define USE_WLED +#include "wled.h" + +#define MAX_REAL_LEDS 64 + + +class LEDs { + public: + CRGB leds[MAX_REAL_LEDS]; + // CRGB led_array[MAX_REAL_LEDS]; + + const static int FRAMES_PER_SECOND = 300; // how often we refresh the strip, in frames per second + const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we refresh the strip, in milliseconds + int num_leds; + + uint16_t fps = 0; + + LEDs(int num_leds=MAX_REAL_LEDS) { + this->num_leds = num_leds; + } + + void setup() { + Serial.println((char *)F("LEDs: ok")); + } + + void reverse() { + for (int i=1; i<8; i++) { + CRGB c = this->leds[i]; + this->leds[i] = this->leds[16-i]; + this->leds[16-i] = c; + } + } + + void show() { + CRGB *external_buffer = WS2812FX::get_external_buffer(); + for (int i = 0; i < num_leds; i++) { + external_buffer[i] = leds[i]; + } + } + + void update(bool reverse=false) { + EVERY_N_MILLISECONDS( this->REFRESH_PERIOD ) { + // Update the LEDs + if (reverse) + this->reverse(); + show(); + this->fps++; + } + + EVERY_N_MILLISECONDS( 1000 ) { + if (this->fps < (FRAMES_PER_SECOND - 30)) { + Serial.print(this->fps); + Serial.println((char *)F(" fps!")); + } + this->fps = 0; + } + } +}; diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h new file mode 100644 index 0000000000..d49f10a1bb --- /dev/null +++ b/usermods/Tubes/particle.h @@ -0,0 +1,227 @@ +#pragma once + +#include "wled.h" +#include "beats.h" +#include "options.h" +// #include "ustd.h" + +#define MAX_PARTICLES 20 +#undef PARTICLE_PALETTES + +class Particle; + +typedef void (*ParticleFn)(Particle *particle, CRGB strip[], uint8_t num_leds); + +extern void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds); +extern void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds); + + +class Particle { + public: + BeatFrame_24_8 born; + BeatFrame_24_8 lifetime; + BeatFrame_24_8 age; + + uint16_t position = 0; + int16_t velocity = 0; + int16_t gravity = 0; + void (*die_fn)(Particle *particle) = NULL; + PenMode pen = Draw; + +#ifdef PARTICLE_PALETTES + CRGBPalette16 palette; // 48 bytes per particle!? +#endif + + CRGB color; + uint16_t brightness; + ParticleFn drawFn; + + Particle(uint16_t position, CRGB color=CRGB::White, PenMode pen=Draw, uint32_t lifetime=20000, ParticleFn drawFn=drawPoint) + { + this->age = 0; + this->position = position; + this->color = color; + this->pen = pen; + this->lifetime = lifetime; + this->brightness = (192<<8); + this->drawFn = drawFn; + } + + void update(BeatFrame_24_8 frame) + { + this->age = frame - this->born; + this->position = this->udelta16(this->position, this->velocity); + this->velocity = this->delta16(this->velocity, this->gravity); + } + + uint16_t age_frac16(BeatFrame_24_8 age) + { + if (age >= this->lifetime) + return 65535; + uint32_t a = age * 65536; + return a / this->lifetime; + } + + uint16_t udelta16(uint16_t x, int16_t dx) + { + if (dx > 0 && 65535-x < dx) + return 65335; + if (dx < 0 && x < -dx) + return 0; + return x + dx; + } + + int16_t delta16(int16_t x, int16_t dx) + { + if (dx > 0 && 32767-x < dx) + return 32767; + if (dx < 0 && x < -32767 - dx) + return -32767; + return x + dx; + } + + CRGB color_at(uint16_t age_frac) { + // Particles get dimmer with age + uint8_t a = age_frac >> 8; + uint8_t brightness = scale8((uint8_t)(this->brightness>>8), 255-a); + +#ifdef PARTICLE_PALETTES + // a black pattern actually means to use the current palette + if (this->color == CRGB(0,0,0)) + return ColorFromPalette(this->palette, a, brightness); +#endif + + uint8_t r = scale8(this->color.r, brightness); + uint8_t g = scale8(this->color.g, brightness); + uint8_t b = scale8(this->color.b, brightness); + return CRGB(r,g,b); + } + + void draw_with_pen(CRGB strip[], int pos, CRGB color) { + CRGB new_color; + + switch (this->pen) { + case Draw: + strip[pos] = color; + break; + + case Blend: + strip[pos] |= color; + break; + + case Erase: + strip[pos] &= color; + break; + + case Invert: + strip[pos] = -strip[pos]; + break; + + case Brighten: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + strip[pos] += new_color; + break; + } + + case Darken: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + strip[pos] -= new_color; + break; + } + + case Flicker: { + uint8_t t = color.getAverageLight(); + new_color = CRGB(t,t,t); + if (millis() % 2) + strip[pos] -= new_color; + else + strip[pos] += new_color; + break; + } + + case White: + strip[pos] = CRGB::White; + break; + + case Black: + strip[pos] = CRGB::Black; + break; + + } + } + +}; + +Particle* particles[5]; +/* PARTICLES +ustd::array particles = ustd::array(5); +*/ +BeatFrame_24_8 particle_beat_frame; + +void addParticle(Particle *particle) { + particle->born = particle_beat_frame; + /* PARTICLES + particles.add(particle); + if (particles.length() > MAX_PARTICLES) { + Particle *old_particle = particles[0]; + delete old_particle; + particles.erase(0); + } + */ +} + + +void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + for (int pos = 0; pos < num_leds; pos++) { + particle->draw_with_pen(strip, pos, c); + } +} + +void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + + uint16_t pos = scale16(particle->position, num_leds-1); + particle->draw_with_pen(strip, pos, c); +} + +void drawRadius(Particle *particle, CRGB strip[], uint8_t num_leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + for (int i = 0; i < radius; i++) { + uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; + nscale8(&c, 1, bright); + + uint8_t y = pos - i; + if (y >= 0 && y < num_leds) + particle->draw_with_pen(strip, y, c); + + if (i == 0) + continue; + + y = pos + i; + if (y >= 0 && y < num_leds) + particle->draw_with_pen(strip, y, c); + } +} + +void drawPop(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + uint16_t pos = scale16(particle->position, num_leds-1); + uint8_t radius = scale16((sin16(age_frac/2) - 32768) * 2, 8); + + drawRadius(particle, strip, num_leds, pos, radius, c); +} + +void drawBeatbox(Particle *particle, CRGB strip[], uint8_t num_leds) { + uint16_t age_frac = particle->age_frac16(particle->age); + CRGB c = particle->color_at(age_frac); + uint16_t pos = scale16(particle->position, num_leds-1); + uint8_t radius = 5; + + drawRadius(particle, strip, num_leds, pos, radius, c, false); +} + diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h new file mode 100644 index 0000000000..86e3883c0c --- /dev/null +++ b/usermods/Tubes/radio.h @@ -0,0 +1,252 @@ +#ifndef RADIO_H +#define RADIO_H + +#include +#include + +#define RADIO_VERSION 1 + +#ifdef USERADIO +NRFLite _radio(Serial); +#endif + +const static uint8_t RADIO_TX_ID = 0; // Radio ID for both trans +const static uint8_t RADIO_RX_ID = 0; // Radio ID for both recv + +const static uint8_t PIN_RADIO_CE = 9; // hardware pins +const static uint8_t PIN_RADIO_CSN = 10; // hardware pins +const static uint8_t PIN_RADIO_MOSI = 11; // hardware pins +const static uint8_t PIN_RADIO_MISO = 12; // hardware pins +const static uint8_t PIN_RADIO_SCK = 13; // hardware pins + +#define RADIO_BITRATE NRFLite::BITRATE1MBPS // { BITRATE2MBPS, BITRATE1MBPS, BITRATE250KBPS } +#define RADIO_CHANNEL 100 + RADIO_VERSION // Channel hop with each version +#define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec + +class Radio; + +typedef uint16_t CommandId; +typedef uint8_t TubeId; + +#define MESSAGE_DATA_MAX_SIZE 25 +typedef struct { + CommandId command; + TubeId tubeId; + TubeId relayId; + byte data[MESSAGE_DATA_MAX_SIZE]; + uint16_t crc = 0; +} RadioMessage; + +class MessageReceiver { + public: + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + +uint16_t calculate_crc( byte *data, byte len ) { + + const unsigned long crc_table[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c + }; + + unsigned long crc = ~0L; + + for ( unsigned int index = 0 ; index < len ; ++index ) { + crc = crc_table[( crc ^ data[index] ) & 0x0f] ^ (crc >> 4); + crc = crc_table[( crc ^ ( data[index] >> 4 )) & 0x0f] ^ (crc >> 4); + crc = ~crc; + } + return crc & 65535; +} + +uint8_t newTubeId() { + return random(10, 250); // Leave room for master +} + +void printMessageData(RadioMessage &message, int size) { + Serial.print(sizeof(message.data)); + Serial.print(F(":")); + for (unsigned int i = 0; i < sizeof(message.data); i++) { + if (message.data[i] < 16) + Serial.print(F("0")); + Serial.print(message.data[i], HEX); + Serial.print(F(" ")); + } + Serial.print(F("[")); + Serial.print(size); + Serial.print(F("] ")); +} + +class Radio { + public: + bool alive = false; // true if radio booted up + bool reported_no_radio = false; + TubeId tubeId = 0; + TubeId masterTubeId = 0; + + unsigned long radioFailures = 0; + unsigned long radioRestarts = 0; + + void setup(bool isMaster) { + if (isMaster) + this->resetId(254); + else + this->resetId(); + +#ifdef USERADIO +#ifdef IS_TEENSY + SPI.setSCK(PIN_RADIO_SCK); + SPI.setMOSI(PIN_RADIO_MOSI); + SPI.setMISO(PIN_RADIO_MISO); +#endif + SPI.begin(); + + this->reported_no_radio = false; + if (_radio.init(RADIO_RX_ID, PIN_RADIO_CE, PIN_RADIO_CSN, RADIO_BITRATE, RADIO_CHANNEL)) { + this->alive = true; + } + Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); + + // Start the radio, but mute & listen for a bit +#endif + } + + void resetId(uint8_t id=0) { + if (id == 0) + id = newTubeId(); + this->tubeId = id; + Serial.print(F("My ID is ")); + Serial.println(this->tubeId); + + if (this->tubeId > this->masterTubeId) + this->masterTubeId = 0; + } + + bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + { + return this->sendCommandFrom(this->tubeId, command, data, size, relayId); + } + + bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + { +#ifndef USERADIO + return true; +#endif + + bool sent = 0; + if (!this->alive) + return sent; + +#ifdef USERADIO + RadioMessage message; + if (size > sizeof(message.data)) { + Serial.println(F("Too big to send")); + return 0; + } + + message.tubeId = id; + message.relayId = relayId; + message.command = command + (RADIO_VERSION << 12); + memset(message.data, 0, sizeof(message.data)); + memcpy(message.data, data, size); + uint16_t crc = calculate_crc(message.data, sizeof(message.data)); + message.crc = crc; + + Serial.print(F("[")); + Serial.print(message.tubeId); + Serial.print(F(": ")); + Serial.print(message.command, HEX); + + sent = _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); + Serial.print(sent ? F(" ok] ") : F(" failed] ")); +#endif + + return sent; + } + + void receiveCommands(MessageReceiver *receiver) + { +#ifdef USERADIO + RadioMessage message; + + if (!this->alive && !this->reported_no_radio) + { + Serial.println(F("No radio")); + this->reported_no_radio = true; + return; + } + + // check for incoming data + while (_radio.hasData()) + { + _radio.readData(&message); + + // Messages must be from a tube with the current version + if ((message.command>>12) != RADIO_VERSION) + return; + + // Ignore relayed messages if we already have a master + if (message.relayId && message.relayId <= this->masterTubeId) + return; + + // Filter out corrupt messages + unsigned long crc = calculate_crc(message.data, sizeof(message.data)); + if (crc != message.crc) { + // Corrupt packet... ignore it. + Serial.print(F("Invalid CRC: ")); + Serial.print(message.crc); + Serial.print(F(" should be ")); + Serial.println(crc); + continue; + } + + // If we detect an ID collision, fix it by choosing a new random one + while (message.tubeId == this->tubeId) { + Serial.print(F("ID collision!")); + this->resetId(); + } + + // Ignore messages from a lower ID + if (message.tubeId < this->tubeId) { + // Don't need to be noisy about relayed messages + if (message.relayId == 0) { + Serial.print(F("Ignoring message from ")); + Serial.println(message.tubeId); + } + return; + } + + if (message.tubeId != 255 && message.tubeId > this->masterTubeId) { + // Found a new master! + this->masterTubeId = message.tubeId; + Serial.print(F("All hail new master ")); + Serial.println(this->masterTubeId); + } + + // Process the command + receiver->onCommand(message.tubeId, message.command & 0xFFF, message.data); + + // Occcasionally relay commands - more frequently if higher ID + uint8_t r = random8(); + if ((r % 3 == 0) && r < this->tubeId) { + Serial.print(F(" (relaying as ")); + Serial.print(this->tubeId); + Serial.print(F(")")); + message.relayId = message.tubeId; + message.tubeId = this->tubeId; + _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); + } + + Serial.println(); + } +#endif + } + +}; + +#endif diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 177f64c14f..dfbf76e9d2 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -168,22 +168,9 @@ class VirtualStrip { brightness = scale8(this->brightness, brightness); for (unsigned i=0; i < num_leds; i++) { -#ifdef DOUBLED - uint8_t pos = (2*i + 1) % this->num_leds; // slope of line is fixed right now at 2:1 -#else uint8_t pos = i; -#endif - CRGB c = this->leds[pos]; -#ifdef DOUBLED - CRGB c1 = this->leds[pos-1]; - CRGB c2 = this->leds[pos+1]; - nblend(c1, c, 128); - nblend(c, c2, 128); - nblend(c, c1, 128); // C is now a weighted average of the three virtual pixels -#endif - nscale8x3(c.r, c.g, c.b, brightness); nscale8x3(c.r, c.g, c.b, this->fader>>8); if (overwrite) From 3fb6d77379afd4998406e34fff83789241e75dee Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 13 Jul 2022 00:56:32 -0700 Subject: [PATCH 108/263] Re-enable particles; fix misc issues --- usermods/Tubes/Tubes.h | 34 ++++++++++++++++--------------- usermods/Tubes/controller.h | 8 ++++---- usermods/Tubes/effects.h | 7 +++---- usermods/Tubes/particle.h | 37 +++++++++++++++++++++------------- usermods/Tubes/radio.h | 2 +- usermods/Tubes/virtual_strip.h | 3 ++- 6 files changed, 51 insertions(+), 40 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index cb167cedd0..ccc87488b1 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -30,15 +30,15 @@ class TubesUsermod : public Usermod { DebugController debug = DebugController(&controller); int* master = NULL; /* master.h deleted */ - void randomize(long seed) { - for (int i = 0; i < seed % 16; i++) { - randomSeed(random(INT_MAX)); - } - random16_add_entropy( random(INT_MAX) ); + void randomize() { + randomSeed(esp_random()); + random16_add_entropy(esp_random()); } public: void setup() { + randomize(); + // Start timing globalTimer.setup(); beats.setup(); @@ -48,8 +48,8 @@ class TubesUsermod : public Usermod { void loop() { - EVERY_N_MILLISECONDS(1000) { - randomize(random(INT_MAX)); + EVERY_N_MILLISECONDS(10000) { + randomize(); } beats.update(); @@ -71,26 +71,28 @@ Aurora Dynamic Smooth Blends Colortwinkles -Fillnoise - maybe Fireworks Fireworks Starburst Flow -Gradient - maybe -Juggle - maybe Lake -Meteor Smooth - maybe Noise 2 Noise 4 Pacifica -Palette - maybe -Phased - maybe Plasma Ripple Running Dual -Saw - maybe -Sinelon Dual - maybe -Tetrix - maybe Twinklecat Twinkleup +MAYBE GOOD PATTERNS +Fillnoise +Gradient +Juggle +Meteor Smooth +Palette +Phased +Saw +Sinelon Dual +Tetrix + */ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 01539dc63c..3856c0e373 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -8,7 +8,7 @@ #include "global_state.h" #include "radio.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 144; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 255; const static CommandId COMMAND_UPDATE = 0x411; const static CommandId COMMAND_NEXT = 0x321; @@ -327,7 +327,9 @@ class PatternController : public MessageReceiver { } uint16_t set_next_effect(uint16_t phrase) { - EffectDef def = gEffects[random8(gEffectCount)]; + uint8_t effect_num = random8(gEffectCount); + + EffectDef def = gEffects[effect_num]; if (def.control.energy > this->energy) def = gEffects[0]; @@ -643,10 +645,8 @@ class PatternController : public MessageReceiver { return; case 'g': - /* PARTICLES for (int i=0; i< 10; i++) addGlitter(); - */ break; case '?': diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index f4ad7532c2..245510aa75 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -100,21 +100,20 @@ class Effects { } void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { - unsigned int len = 0; /* PARTICLES particles.length(); */ + unsigned int len = numParticles; for (unsigned i=len; i > 0; i--) { Particle *particle = particles[i-1]; particle->update(frame); if (particle->age > particle->lifetime) { - delete particle; - /* PARTICLES particles.erase(i-1); */ + removeParticle(i-1); continue; } } } void draw(CRGB strip[], uint8_t num_leds) { - uint8_t len = 0; /* PARTICLES particles.length(); */ + uint8_t len = numParticles; for (uint8_t i=0; idrawFn(particle, strip, num_leds); diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d49f10a1bb..d31e4185d7 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -3,9 +3,8 @@ #include "wled.h" #include "beats.h" #include "options.h" -// #include "ustd.h" -#define MAX_PARTICLES 20 +#define MAX_PARTICLES 80 #undef PARTICLE_PALETTES class Particle; @@ -154,25 +153,35 @@ class Particle { }; -Particle* particles[5]; -/* PARTICLES -ustd::array particles = ustd::array(5); -*/ +Particle* particles[MAX_PARTICLES]; BeatFrame_24_8 particle_beat_frame; +uint8_t numParticles = 0; + +void removeParticle(uint8_t i) { + if (i >= numParticles) + return; + + // Free the memory of the old particle + Particle *old_particle = particles[i]; + delete old_particle; + + // Reset the current free particle + int rest = numParticles - i; + if (rest > 0) { + memmove(&particles[i], &particles[i+1], sizeof(particles[0]) * rest); + } + + numParticles -= 1; +} void addParticle(Particle *particle) { particle->born = particle_beat_frame; - /* PARTICLES - particles.add(particle); - if (particles.length() > MAX_PARTICLES) { - Particle *old_particle = particles[0]; - delete old_particle; - particles.erase(0); + if (numParticles >= MAX_PARTICLES) { + removeParticle(0); } - */ + particles[numParticles++] = particle; } - void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 86e3883c0c..53cb3df15a 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -2,11 +2,11 @@ #define RADIO_H #include -#include #define RADIO_VERSION 1 #ifdef USERADIO +#include NRFLite _radio(Serial); #endif diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index dfbf76e9d2..2312724709 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -35,7 +35,8 @@ BeatFrame_24_8 swing(BeatFrame_24_8 frame) { } class VirtualStrip { - const static uint16_t DEF_BRIGHT = 192; + // Let WLED do the dimming + const static uint16_t DEF_BRIGHT = 255; public: CRGB leds[MAX_VIRTUAL_LEDS]; From c1503573817d6e7afce60e7fce65a93144e9b112 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 13 Jul 2022 01:05:37 -0700 Subject: [PATCH 109/263] lower default brightness a bit to emphasize effects --- usermods/Tubes/controller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3856c0e373..f873863a3a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -8,7 +8,7 @@ #include "global_state.h" #include "radio.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 255; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static CommandId COMMAND_UPDATE = 0x411; const static CommandId COMMAND_NEXT = 0x321; From 4da31409b342781a69c2b1d260ef97e7ed4c413a Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 18 Jul 2022 00:35:22 -0700 Subject: [PATCH 110/263] Introduce BLE --- usermods/Tubes/controller.h | 25 +++++----- usermods/Tubes/radio.h | 98 +++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 64 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index f873863a3a..fcba001e19 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -10,13 +10,14 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -const static CommandId COMMAND_UPDATE = 0x411; -const static CommandId COMMAND_NEXT = 0x321; -const static CommandId COMMAND_RESET = 0x911; -const static CommandId COMMAND_FIREWORK = 0xFFF; -const static CommandId COMMAND_HELLO = 0x000; -const static CommandId COMMAND_OPTIONS = 0x123; -const static CommandId COMMAND_BRIGHTNESS = 0x888; +const static CommandId COMMAND_HELLO = 0x00; +const static CommandId COMMAND_OPTIONS = 0x10; +const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_NEXT = 0x30; +const static CommandId COMMAND_RESET = 0xF0; + +const static CommandId COMMAND_BRIGHTNESS = 0x80; +const static CommandId COMMAND_FIREWORK = 0x90; typedef struct { @@ -139,9 +140,9 @@ class PatternController : public MessageReceiver { this->read_keys(); // If master has expired, clear masterId - if (this->radio->masterTubeId && this->slaveTimer.ended()) { + if (this->radio->uplinkTubeId && this->slaveTimer.ended()) { Serial.println(F("I have no master")); - this->radio->masterTubeId = 0; + this->radio->uplinkTubeId = 0; } // Update patterns to the beat @@ -162,7 +163,7 @@ class PatternController : public MessageReceiver { } // If alone or master, send out updates - if (!this->radio->masterTubeId and this->updateTimer.ended()) { + if (!this->radio->uplinkTubeId and this->updateTimer.ended()) { this->send_update(); } @@ -462,7 +463,7 @@ class PatternController : public MessageReceiver { case COMMAND_NEXT: { Serial.print(F(" next ")); - if (fromId < this->radio->masterTubeId) { + if (fromId < this->radio->uplinkTubeId) { Serial.print(F(" (ignoring)")); return; } @@ -475,7 +476,7 @@ class PatternController : public MessageReceiver { case COMMAND_UPDATE: { Serial.print(F(" update ")); - if (fromId < this->radio->masterTubeId) { + if (fromId < this->radio->uplinkTubeId) { Serial.print(F(" (ignoring)")); return; } diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 53cb3df15a..d6ae60bec9 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -1,31 +1,25 @@ -#ifndef RADIO_H -#define RADIO_H +#pragma once #include +#include #define RADIO_VERSION 1 +// #define USEBLE -#ifdef USERADIO -#include -NRFLite _radio(Serial); -#endif +#ifdef USEBLE +#include "bluetooth.h" +#include -const static uint8_t RADIO_TX_ID = 0; // Radio ID for both trans -const static uint8_t RADIO_RX_ID = 0; // Radio ID for both recv +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -const static uint8_t PIN_RADIO_CE = 9; // hardware pins -const static uint8_t PIN_RADIO_CSN = 10; // hardware pins -const static uint8_t PIN_RADIO_MOSI = 11; // hardware pins -const static uint8_t PIN_RADIO_MISO = 12; // hardware pins -const static uint8_t PIN_RADIO_SCK = 13; // hardware pins +static NimBLEUUID dataUuid(SERVICE_UUID); +#endif -#define RADIO_BITRATE NRFLite::BITRATE1MBPS // { BITRATE2MBPS, BITRATE1MBPS, BITRATE250KBPS } -#define RADIO_CHANNEL 100 + RADIO_VERSION // Channel hop with each version #define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec class Radio; -typedef uint16_t CommandId; +typedef uint8_t CommandId; typedef uint8_t TubeId; #define MESSAGE_DATA_MAX_SIZE 25 @@ -87,7 +81,8 @@ class Radio { bool alive = false; // true if radio booted up bool reported_no_radio = false; TubeId tubeId = 0; - TubeId masterTubeId = 0; + TubeId uplinkTubeId = 0; + char tube_name[20]; unsigned long radioFailures = 0; unsigned long radioRestarts = 0; @@ -97,23 +92,16 @@ class Radio { this->resetId(254); else this->resetId(); - -#ifdef USERADIO -#ifdef IS_TEENSY - SPI.setSCK(PIN_RADIO_SCK); - SPI.setMOSI(PIN_RADIO_MOSI); - SPI.setMISO(PIN_RADIO_MISO); + +#ifdef USEBLE + ble_setup(); #endif - SPI.begin(); - - this->reported_no_radio = false; - if (_radio.init(RADIO_RX_ID, PIN_RADIO_CE, PIN_RADIO_CSN, RADIO_BITRATE, RADIO_CHANNEL)) { - this->alive = true; - } - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); + Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit -#endif + } + + void update() { } void resetId(uint8_t id=0) { @@ -123,8 +111,18 @@ class Radio { Serial.print(F("My ID is ")); Serial.println(this->tubeId); - if (this->tubeId > this->masterTubeId) - this->masterTubeId = 0; + if (this->tubeId > this->uplinkTubeId) + this->uplinkTubeId = 0; + +#ifdef USEBLE + if (this->alive) + NimBLEDevice::deinit(false); + + sprintf(tube_name, "Tube %02X", this->tubeId); + NimBLEDevice::init(std::string(tube_name)); + delay(1000); + this->alive = true; +#endif } bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) @@ -134,24 +132,19 @@ class Radio { bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) { -#ifndef USERADIO - return true; -#endif - - bool sent = 0; + bool sent = false; if (!this->alive) - return sent; + return true; -#ifdef USERADIO RadioMessage message; if (size > sizeof(message.data)) { Serial.println(F("Too big to send")); - return 0; + return false; } message.tubeId = id; message.relayId = relayId; - message.command = command + (RADIO_VERSION << 12); + message.command = command + RADIO_VERSION; memset(message.data, 0, sizeof(message.data)); memcpy(message.data, data, size); uint16_t crc = calculate_crc(message.data, sizeof(message.data)); @@ -161,11 +154,14 @@ class Radio { Serial.print(message.tubeId); Serial.print(F(": ")); Serial.print(message.command, HEX); - - sent = _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); - Serial.print(sent ? F(" ok] ") : F(" failed] ")); + +#ifdef USEBLE + sent = ble_broadcast((byte *)&message, sizeof(message)); +#else + sent = true; #endif + Serial.print(sent ? F(" ok] ") : F(" failed] ")); return sent; } @@ -191,7 +187,7 @@ class Radio { return; // Ignore relayed messages if we already have a master - if (message.relayId && message.relayId <= this->masterTubeId) + if (message.relayId && message.relayId <= this->uplinkTubeId) return; // Filter out corrupt messages @@ -221,11 +217,11 @@ class Radio { return; } - if (message.tubeId != 255 && message.tubeId > this->masterTubeId) { + if (message.tubeId != 255 && message.tubeId > this->uplinkTubeId) { // Found a new master! - this->masterTubeId = message.tubeId; - Serial.print(F("All hail new master ")); - Serial.println(this->masterTubeId); + this->uplinkTubeId = message.tubeId; + Serial.print(F("My new uplink is ")); + Serial.println(this->uplinkTubeId); } // Process the command @@ -248,5 +244,3 @@ class Radio { } }; - -#endif From 5123b0ab8ba7deacb062717aeaf2fd2fc24ead8e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 18 Jul 2022 00:35:46 -0700 Subject: [PATCH 111/263] Introduce BLE --- usermods/Tubes/Tubes.h | 1 + usermods/Tubes/bluetooth.h | 217 +++++++++++++++++++++++++++++++++++++ usermods/Tubes/debug.h | 8 +- 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 usermods/Tubes/bluetooth.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index ccc87488b1..d82375b659 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -55,6 +55,7 @@ class TubesUsermod : public Usermod { beats.update(); controller.update(); debug.update(); + radio.update(); // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h new file mode 100644 index 0000000000..fa2e68c5dd --- /dev/null +++ b/usermods/Tubes/bluetooth.h @@ -0,0 +1,217 @@ +#pragma once + +#include +#include + +static NimBLEServer* pServer; + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer) { + Serial.println("Client connected"); + Serial.println("Multi-connect support: start advertising"); + NimBLEDevice::startAdvertising(); + }; + + /** Alternative onConnect() method to extract details of the connection. + * See: src/ble_gap.h for the details of the ble_gap_conn_desc struct. + */ + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + Serial.print("Client address: "); + Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + /** We can use the connection handle here to ask for different connection parameters. + * Args: connection handle, min connection interval, max connection interval + * latency, supervision timeout. + * Units; Min/Max Intervals: 1.25 millisecond increments. + * Latency: number of intervals allowed to skip. + * Timeout: 10 millisecond increments, try for 5x interval time for best results. + */ + pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60); + }; + + void onDisconnect(NimBLEServer* pServer) { + Serial.println("Client disconnected - start advertising"); + NimBLEDevice::startAdvertising(); + }; + + void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) { + Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); + }; + +/********************* Security handled here ********************** +****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + Serial.println("Server Passkey Request"); + /** This should return a random 6 digit number for security + * or make your own static passkey as done here. + */ + return 123456; + }; + + bool onConfirmPIN(uint32_t pass_key){ + Serial.print("The passkey YES/NO number: ");Serial.println(pass_key); + /** Return false if passkeys don't match. */ + return true; + }; + + void onAuthenticationComplete(ble_gap_conn_desc* desc){ + /** Check that encryption was successful, if not we disconnect the client */ + if(!desc->sec_state.encrypted) { + NimBLEDevice::getServer()->disconnect(desc->conn_handle); + Serial.println("Encrypt connection failed - disconnecting client"); + return; + } + Serial.println("Starting BLE work!"); + }; +}; + +/** Handler class for characteristic actions */ +class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { + void onRead(NimBLECharacteristic* pCharacteristic){ + Serial.print(pCharacteristic->getUUID().toString().c_str()); + Serial.print(": onRead(), value: "); + Serial.println(pCharacteristic->getValue().c_str()); + }; + + void onWrite(NimBLECharacteristic* pCharacteristic) { + Serial.print(pCharacteristic->getUUID().toString().c_str()); + Serial.print(": onWrite(), value: "); + Serial.println(pCharacteristic->getValue().c_str()); + }; + + /** Called before notification or indication is sent, + * the value can be changed here before sending if desired. + */ + void onNotify(NimBLECharacteristic* pCharacteristic) { + Serial.println("Sending notification to clients"); + }; + + + /** The status returned in status is defined in NimBLECharacteristic.h. + * The value returned in code is the NimBLE host return code. + */ + void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) { + String str = ("Notification/Indication status code: "); + str += status; + str += ", return code: "; + str += code; + str += ", "; + str += NimBLEUtils::returnCodeToString(code); + Serial.println(str); + }; + + void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) { + String str = "Client ID: "; + str += desc->conn_handle; + str += " Address: "; + str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str(); + if(subValue == 0) { + str += " Unsubscribed to "; + }else if(subValue == 1) { + str += " Subscribed to notfications for "; + } else if(subValue == 2) { + str += " Subscribed to indications for "; + } else if(subValue == 3) { + str += " Subscribed to notifications and indications for "; + } + str += std::string(pCharacteristic->getUUID()).c_str(); + + Serial.println(str); + }; +}; + +/** Handler class for descriptor actions */ +class DescriptorCallbacks : public NimBLEDescriptorCallbacks { + void onWrite(NimBLEDescriptor* pDescriptor) { + std::string dscVal = pDescriptor->getValue(); + Serial.print("Descriptor witten value:"); + Serial.println(dscVal.c_str()); + }; + + void onRead(NimBLEDescriptor* pDescriptor) { + Serial.print(pDescriptor->getUUID().toString().c_str()); + Serial.println(" Descriptor read"); + }; +}; + + +/** Define callback instances globally to use for multiple Charateristics \ Descriptors */ +static DescriptorCallbacks dscCallbacks; +static CharacteristicCallbacks chrCallbacks; + + +void ble_setup() { + esp_coex_preference_set(ESP_COEX_PREFER_BT); + return; + +// /** Optional: set the transmit power, default is 3db */ +// #ifdef ESP_PLATFORM +// NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ +// #else +// NimBLEDevice::setPower(9); /** +9db */ +// #endif + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); + NimBLEDevice::setSecurityAuth(false, false, true); + + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks()); + + NimBLEService* pTubeService = pServer->createService("D00B"); + NimBLECharacteristic* pFoodCharacteristic = pTubeService->createCharacteristic( + "FEED", + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + ); + + pFoodCharacteristic->setValue("Fries"); + pFoodCharacteristic->setCallbacks(&chrCallbacks); + + /** Start the services when finished creating all Characteristics and Descriptors */ + pTubeService->start(); + + /** Add the services to the advertisment data **/ + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(pTubeService->getUUID()); + pAdvertising->setScanResponse(false); + pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); + // pAdvertising->start(); + + Serial.println("Advertising Started"); + delay(1000); +} + + +bool ble_broadcast(byte *data, int size) { + return true; + + // Broadcast the current effect state to every connected client + if (pServer->getConnectedCount() == 0) + return true; + + NimBLEService* pTubeService = pServer->getServiceByUUID("D00B"); + if (!pTubeService) + return true; + + NimBLECharacteristic* pFoodCharacteristic = pTubeService->getCharacteristic("FEED"); + if(!pFoodCharacteristic) + return true; + + // Update the characteristic + byte buffer[40]; + if (size > 40) { + Serial.println("Too much data"); + return false; + } + memcpy(buffer, data, size); + + pFoodCharacteristic->setValue(buffer); + pFoodCharacteristic->notify(true); + + return true; +} \ No newline at end of file diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 8f4717ab89..6134fab0db 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -27,6 +27,12 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { + Serial.printf("IP: %u.%u.%u.%u ", + WiFi.localIP()[0], + WiFi.localIP()[1], + WiFi.localIP()[2], + WiFi.localIP()[3] + ); Serial.print(F("Free memory: ")); Serial.println( freeMemory() ); } @@ -40,7 +46,7 @@ class DebugController { uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->masterTubeId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->radio->uplinkTubeId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { From 2e3704c60056f30f326d91f167d1e12181bedc4d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 21 Jul 2022 00:55:29 -0700 Subject: [PATCH 112/263] More BLE investigation --- usermods/Tubes/bluetooth.h | 34 ++++++++++++++++++++++++++-------- usermods/Tubes/radio.h | 22 +--------------------- wled00/wled.h | 1 + 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index fa2e68c5dd..21c8e66e14 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -3,8 +3,11 @@ #include #include +#define USEBLE static NimBLEServer* pServer; +bool initialized = false; + /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ class ServerCallbacks: public NimBLEServerCallbacks { @@ -141,16 +144,29 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; +void ble_init(char *tube_name) { +#ifndef USEBLE + return; +#endif + if (initialized) + NimBLEDevice::deinit(false); + + NimBLEDevice::init(std::string(tube_name)); + initialized = true; +} + void ble_setup() { - esp_coex_preference_set(ESP_COEX_PREFER_BT); +#ifndef USEBLE return; +#endif + esp_coex_preference_set(ESP_COEX_PREFER_BT); -// /** Optional: set the transmit power, default is 3db */ -// #ifdef ESP_PLATFORM -// NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ -// #else -// NimBLEDevice::setPower(9); /** +9db */ -// #endif + /** Optional: set the transmit power, default is 3db */ +#ifdef ESP_PLATFORM + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ +#else + NimBLEDevice::setPower(9); /** +9db */ +#endif /** Set the IO capabilities of the device, each option will trigger a different pairing method. * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing @@ -188,8 +204,10 @@ void ble_setup() { bool ble_broadcast(byte *data, int size) { +#ifndef USEBLE return true; - +#endif + // Broadcast the current effect state to every connected client if (pServer->getConnectedCount() == 0) return true; diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index d6ae60bec9..aa826ded72 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -4,16 +4,8 @@ #include #define RADIO_VERSION 1 -// #define USEBLE -#ifdef USEBLE #include "bluetooth.h" -#include - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" - -static NimBLEUUID dataUuid(SERVICE_UUID); -#endif #define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec @@ -93,9 +85,7 @@ class Radio { else this->resetId(); -#ifdef USEBLE ble_setup(); -#endif Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit @@ -114,15 +104,9 @@ class Radio { if (this->tubeId > this->uplinkTubeId) this->uplinkTubeId = 0; -#ifdef USEBLE - if (this->alive) - NimBLEDevice::deinit(false); - sprintf(tube_name, "Tube %02X", this->tubeId); - NimBLEDevice::init(std::string(tube_name)); - delay(1000); + ble_init(tube_name); this->alive = true; -#endif } bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) @@ -155,11 +139,7 @@ class Radio { Serial.print(F(": ")); Serial.print(message.command, HEX); -#ifdef USEBLE sent = ble_broadcast((byte *)&message, sizeof(message)); -#else - sent = true; -#endif Serial.print(sent ? F(" ok] ") : F(" failed] ")); return sent; diff --git a/wled00/wled.h b/wled00/wled.h index 64829e5e7d..349a38b796 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -16,6 +16,7 @@ #define WLED_DISABLE_LOXONE #define WLED_DISABLE_ALEXA #define WLED_DISABLE_INFRARED +#define WLED_DISABLE_CRONIXIE // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From 0d2519e3345811d2caebae010eaded757e97df17 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 21 Jul 2022 03:02:54 -0700 Subject: [PATCH 113/263] Fix BLE initialization --- usermods/Tubes/bluetooth.h | 43 +++++++++++++++++--------------------- usermods/Tubes/radio.h | 9 +++++--- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 21c8e66e14..95d17607d3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -6,7 +6,7 @@ #define USEBLE static NimBLEServer* pServer; -bool initialized = false; +bool bluetooth_on = false; /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ @@ -144,23 +144,7 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; -void ble_init(char *tube_name) { -#ifndef USEBLE - return; -#endif - if (initialized) - NimBLEDevice::deinit(false); - - NimBLEDevice::init(std::string(tube_name)); - initialized = true; -} - void ble_setup() { -#ifndef USEBLE - return; -#endif - esp_coex_preference_set(ESP_COEX_PREFER_BT); - /** Optional: set the transmit power, default is 3db */ #ifdef ESP_PLATFORM NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ @@ -196,17 +180,14 @@ void ble_setup() { pAdvertising->addServiceUUID(pTubeService->getUUID()); pAdvertising->setScanResponse(false); pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - // pAdvertising->start(); + pAdvertising->start(); Serial.println("Advertising Started"); - delay(1000); } - bool ble_broadcast(byte *data, int size) { -#ifndef USEBLE - return true; -#endif + if (!bluetooth_on) + return true; // Broadcast the current effect state to every connected client if (pServer->getConnectedCount() == 0) @@ -232,4 +213,18 @@ bool ble_broadcast(byte *data, int size) { pFoodCharacteristic->notify(true); return true; -} \ No newline at end of file +} + +void ble_init(char *name) { + if (bluetooth_on) { + NimBLEDevice::deinit(false); + bluetooth_on = false; + } + + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + esp_coex_preference_set(ESP_COEX_PREFER_BT); + NimBLEDevice::init(std::string(name)); + ble_setup(); + bluetooth_on = true; +} + diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index aa826ded72..1dff0a82d2 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -85,13 +85,15 @@ class Radio { else this->resetId(); - ble_setup(); - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit } void update() { + if (millis() > 500 && !bluetooth_on) { + Serial.println("INITIALIZE BLUETOOTH"); + ble_init(tube_name); + } } void resetId(uint8_t id=0) { @@ -105,7 +107,8 @@ class Radio { this->uplinkTubeId = 0; sprintf(tube_name, "Tube %02X", this->tubeId); - ble_init(tube_name); + if (bluetooth_on) + ble_init(tube_name); this->alive = true; } From f640e3bad3bbaaefa35b923a42d2a2f08e6f95eb Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 22 Jul 2022 05:03:36 -0700 Subject: [PATCH 114/263] New bluetooth mesh code --- usermods/Tubes/bluetooth.h | 282 ++++++++++++++++++++++++++---------- usermods/Tubes/controller.h | 40 +---- usermods/Tubes/debug.h | 4 +- usermods/Tubes/radio.h | 57 ++------ 4 files changed, 231 insertions(+), 152 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 95d17607d3..52217e1315 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -3,10 +3,16 @@ #include #include -#define USEBLE -static NimBLEServer* pServer; -bool bluetooth_on = false; +typedef uint16_t MeshId; + +typedef struct { + MeshId id = 0; + MeshId uplinkId = 0; +} MeshIds; + + +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ @@ -138,93 +144,223 @@ class DescriptorCallbacks : public NimBLEDescriptorCallbacks { }; }; - /** Define callback instances globally to use for multiple Charateristics \ Descriptors */ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; -void ble_setup() { - /** Optional: set the transmit power, default is 3db */ -#ifdef ESP_PLATFORM - NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ -#else - NimBLEDevice::setPower(9); /** +9db */ -#endif +class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { + public: + bool alive = false; // true if radio booted up - /** Set the IO capabilities of the device, each option will trigger a different pairing method. - * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing - * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing - * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing - */ - NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); - NimBLEDevice::setSecurityAuth(false, false, true); + MeshIds ids; + byte buffer[100]; + char node_name[20]; - pServer = NimBLEDevice::createServer(); - pServer->setCallbacks(new ServerCallbacks()); + uint16_t serviceUUID = 0xD00F; - NimBLEService* pTubeService = pServer->createService("D00B"); - NimBLECharacteristic* pFoodCharacteristic = pTubeService->createCharacteristic( - "FEED", - NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST - ); + NimBLEServer* pServer = NULL; + NimBLEService* pService = NULL; + NimBLEScan* pScanner = NULL; - pFoodCharacteristic->setValue("Fries"); - pFoodCharacteristic->setCallbacks(&chrCallbacks); + Timer uplinkTimer; - /** Start the services when finished creating all Characteristics and Descriptors */ - pTubeService->start(); + MeshId newMeshId() { + return random(0, 4000); + } - /** Add the services to the advertisment data **/ - NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); - pAdvertising->addServiceUUID(pTubeService->getUUID()); - pAdvertising->setScanResponse(false); - pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - pAdvertising->start(); + void advertise() { + if (!pService) + return; - Serial.println("Advertising Started"); -} + // Add the services to the advertisement data + auto pAdvertising = NimBLEDevice::getAdvertising(); + if (!pAdvertising) + return; -bool ble_broadcast(byte *data, int size) { - if (!bluetooth_on) - return true; + auto service_data = std::string((char *)&ids, sizeof(ids)); + if (ids.uplinkId) + sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); + else + sprintf(node_name, "Tube %03X", ids.id); - // Broadcast the current effect state to every connected client - if (pServer->getConnectedCount() == 0) - return true; + // // // Reset the device name + // NimBLEDevice::deinit(false); + // NimBLEDevice::init(node_name); - NimBLEService* pTubeService = pServer->getServiceByUUID("D00B"); - if (!pTubeService) - return true; + // Set advertisement + pAdvertising->stop(); + pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); + pAdvertising->start(); - NimBLECharacteristic* pFoodCharacteristic = pTubeService->getCharacteristic("FEED"); - if(!pFoodCharacteristic) - return true; - - // Update the characteristic - byte buffer[40]; - if (size > 40) { - Serial.println("Too much data"); - return false; + Serial.printf("Advertising %s", node_name); + Serial.println(); + } + + void init_service() { + /** Optional: set the transmit power, default is 3db */ + #ifdef ESP_PLATFORM + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ + #else + NimBLEDevice::setPower(9); /** +9db */ + #endif + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); + NimBLEDevice::setSecurityAuth(false, false, true); + + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks()); + + pService = pServer->createService("D00B"); + NimBLECharacteristic* pCharacteristic = pService->createCharacteristic( + "FEED", + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + ); + + pCharacteristic->setValue("Test"); + pCharacteristic->setCallbacks(&chrCallbacks); + + /** Start the services when finished creating all Characteristics and Descriptors */ + pService->start(); + + /** Add the services to the advertisement data **/ + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(pService->getUUID()); + pAdvertising->setScanResponse(false); + pAdvertising->setAppearance(0x07C6); // Multi-color LED array + pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); + + advertise(); } - memcpy(buffer, data, size); - - pFoodCharacteristic->setValue(buffer); - pFoodCharacteristic->notify(true); - - return true; -} - -void ble_init(char *name) { - if (bluetooth_on) { - NimBLEDevice::deinit(false); - bluetooth_on = false; + + void init_scanner() { + NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); + NimBLEDevice::setScanDuplicateCacheSize(200); + + pScanner = NimBLEDevice::getScan(); //create new scan + // Set the callback for when devices are discovered, no duplicates. + pScanner->setAdvertisedDeviceCallbacks(this, false); + pScanner->setActiveScan(false); // Don't request data (it uses more energy) + pScanner->setInterval(200); // How often the scan occurs / switches channels; in milliseconds, + pScanner->setWindow(80); // How long to scan during the interval; in milliseconds. + pScanner->setMaxResults(0); // do not store the scan results, use callback only. + } + + void init() { + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + esp_coex_preference_set(ESP_COEX_PREFER_BT); + NimBLEDevice::init(std::string("Tube")); + init_scanner(); + init_service(); + this->alive = true; + } + + void uplink_ping() { + // Track the last time we received a message from our master + this->uplinkTimer.start(UPLINK_TIMEOUT); } - esp_wifi_set_ps(WIFI_PS_MIN_MODEM); - esp_coex_preference_set(ESP_COEX_PREFER_BT); - NimBLEDevice::init(std::string(name)); - ble_setup(); - bluetooth_on = true; -} + void update() { + // Don't do anything for the first half-second to avoid crashing WiFi + if (millis() < 500) + return; + + if (!this->alive) { + this->init(); + } + + // Check the last time we heard from the uplink node + if (is_following() && this->uplinkTimer.ended()) { + Serial.println("Uplink lost"); + follow(0); + } + + if (!this->pScanner->isScanning()) { + // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. + this->pScanner->start(0, nullptr, false); + } + } + void broadcast(byte *data, int size) { + if (size > sizeof(buffer)) { + Serial.println("Too much data"); + return; + } + + memset(buffer, 0, sizeof(buffer)); + memcpy(buffer, data, size); + + if (!pServer) + return; + + // Broadcast the current effect state to every connected client + if (pServer->getConnectedCount() == 0) + return; + + if (!pService) + return; + + NimBLECharacteristic* pCharacteristic = pService->getCharacteristic("FEED"); + if(!pCharacteristic) + return; + + // Update the characteristic + pCharacteristic->setValue(buffer); + pCharacteristic->notify(true); + } + + void reset(MeshId id = 0) { + if (id == 0) + id = newMeshId(); + this->ids.id = id; + + Serial.printf("My ID is %03X", this->ids.id); + if (this->ids.id > this->ids.uplinkId) + this->ids.uplinkId = 0; + + advertise(); + } + + void follow(MeshId uplinkId) { + if (this->ids.uplinkId == uplinkId) + return; + + this->ids.uplinkId = uplinkId; + advertise(); + } + + bool is_following() { + return this->ids.uplinkId != 0; + } + + // ====== CALLBACKS ======= + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { + if (!advertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) + return; + + // Make sure it's booted up and advertising data + auto data = advertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); + if (!data.length()) + return; + + MeshIds data_ids; + memcpy(&data_ids, data.c_str(), data.length()); + Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); + + if (data_ids.id >= ids.uplinkId) { + follow(data_ids.id); + uplink_ping(); + } + + Serial.printf("Found: %s: %s\n", + std::string(advertisedDevice->getAddress()).c_str(), + std::string(advertisedDevice->getName()).c_str() + ); + } + +}; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index fcba001e19..8e0c2c435b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -74,7 +74,6 @@ class PatternController : public MessageReceiver { Timer graphicsTimer; Timer updateTimer; - Timer slaveTimer; #ifdef USELCD Lcd *lcd; @@ -131,7 +130,6 @@ class PatternController : public MessageReceiver { this->radio->setup(this->isMaster); this->radio->sendCommand(COMMAND_HELLO); - this->slaveTimer.start(RADIO_SENDPERIOD * 3); // Assume we're a slave at first, just listen for a master. this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to } @@ -139,12 +137,6 @@ class PatternController : public MessageReceiver { { this->read_keys(); - // If master has expired, clear masterId - if (this->radio->uplinkTubeId && this->slaveTimer.ended()) { - Serial.println(F("I have no master")); - this->radio->uplinkTubeId = 0; - } - // Update patterns to the beat this->update_beat(); @@ -162,8 +154,8 @@ class PatternController : public MessageReceiver { this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); } - // If alone or master, send out updates - if (!this->radio->uplinkTubeId and this->updateTimer.ended()) { + // Update current status + if (this->updateTimer.ended()) { this->send_update(); } @@ -220,19 +212,8 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - if (this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state))) { - this->radio->radioFailures = 0; - this->updateTimer.snooze(RADIO_SENDPERIOD); - } else { - // might have been a collision. Back off by a small amount determined by ID - Serial.println(F("Radio update failed")); - this->updateTimer.snooze( this->radio->tubeId & 0x7F ); - this->radio->radioFailures++; - if (this->radio->radioFailures > 100) { - this->radio->setup(this->isMaster); - this->radio->radioRestarts++; - } - } + this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state)); + this->updateTimer.snooze(RADIO_SENDPERIOD); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -463,10 +444,6 @@ class PatternController : public MessageReceiver { case COMMAND_NEXT: { Serial.print(F(" next ")); - if (fromId < this->radio->uplinkTubeId) { - Serial.print(F(" (ignoring)")); - return; - } memcpy(&this->next_state, data, sizeof(TubeState)); this->next_state.print(); @@ -476,19 +453,12 @@ class PatternController : public MessageReceiver { case COMMAND_UPDATE: { Serial.print(F(" update ")); - if (fromId < this->radio->uplinkTubeId) { - Serial.print(F(" (ignoring)")); - return; - } TubeState state; memcpy(&state, data, sizeof(TubeState)); state.print(); Serial.print(F(" (obeying)")); - // Track the last time we received a message from our master - this->slaveTimer.start(RADIO_SENDPERIOD * 8); - // Catch up to this state this->load_pattern(state); this->load_palette(state); @@ -559,7 +529,7 @@ class PatternController : public MessageReceiver { break; case 'i': - this->radio->resetId(arg >> 8); + this->radio->mesh_node.reset(arg >> 8); break; case 'd': diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 6134fab0db..861ae393bd 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -43,10 +43,10 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->radio->tubeId, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->radio->mesh_node.ids.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->uplinkTubeId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->radio->mesh_node.ids.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h index 1dff0a82d2..3f44c41754 100644 --- a/usermods/Tubes/radio.h +++ b/usermods/Tubes/radio.h @@ -12,17 +12,15 @@ class Radio; typedef uint8_t CommandId; -typedef uint8_t TubeId; #define MESSAGE_DATA_MAX_SIZE 25 typedef struct { CommandId command; - TubeId tubeId; - TubeId relayId; byte data[MESSAGE_DATA_MAX_SIZE]; uint16_t crc = 0; } RadioMessage; + class MessageReceiver { public: @@ -50,9 +48,6 @@ uint16_t calculate_crc( byte *data, byte len ) { return crc & 65535; } -uint8_t newTubeId() { - return random(10, 250); // Leave room for master -} void printMessageData(RadioMessage &message, int size) { Serial.print(sizeof(message.data)); @@ -72,52 +67,32 @@ class Radio { public: bool alive = false; // true if radio booted up bool reported_no_radio = false; - TubeId tubeId = 0; - TubeId uplinkTubeId = 0; - char tube_name[20]; + + BLEMeshNode mesh_node = BLEMeshNode(); unsigned long radioFailures = 0; unsigned long radioRestarts = 0; void setup(bool isMaster) { if (isMaster) - this->resetId(254); + mesh_node.reset(4500); else - this->resetId(); + mesh_node.reset(); Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); // Start the radio, but mute & listen for a bit } void update() { - if (millis() > 500 && !bluetooth_on) { - Serial.println("INITIALIZE BLUETOOTH"); - ble_init(tube_name); - } - } - - void resetId(uint8_t id=0) { - if (id == 0) - id = newTubeId(); - this->tubeId = id; - Serial.print(F("My ID is ")); - Serial.println(this->tubeId); - - if (this->tubeId > this->uplinkTubeId) - this->uplinkTubeId = 0; - - sprintf(tube_name, "Tube %02X", this->tubeId); - if (bluetooth_on) - ble_init(tube_name); - this->alive = true; + mesh_node.update(); } - bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) { - return this->sendCommandFrom(this->tubeId, command, data, size, relayId); + return this->sendCommandFrom(0, command, data, size, relayId); } - bool sendCommandFrom(TubeId id, uint32_t command, void *data=0, uint8_t size=0, TubeId relayId=0) + bool sendCommandFrom(MeshId id, uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) { bool sent = false; if (!this->alive) @@ -129,8 +104,6 @@ class Radio { return false; } - message.tubeId = id; - message.relayId = relayId; message.command = command + RADIO_VERSION; memset(message.data, 0, sizeof(message.data)); memcpy(message.data, data, size); @@ -138,14 +111,14 @@ class Radio { message.crc = crc; Serial.print(F("[")); - Serial.print(message.tubeId); + Serial.print(mesh_node.ids.id); Serial.print(F(": ")); Serial.print(message.command, HEX); - sent = ble_broadcast((byte *)&message, sizeof(message)); + mesh_node.broadcast((byte *)&message, sizeof(message)); Serial.print(sent ? F(" ok] ") : F(" failed] ")); - return sent; + return true; } void receiveCommands(MessageReceiver *receiver) @@ -170,7 +143,7 @@ class Radio { return; // Ignore relayed messages if we already have a master - if (message.relayId && message.relayId <= this->uplinkTubeId) + if (message.uplinkId && message.uplinkId <= this->uplinkTubeId) return; // Filter out corrupt messages @@ -193,7 +166,7 @@ class Radio { // Ignore messages from a lower ID if (message.tubeId < this->tubeId) { // Don't need to be noisy about relayed messages - if (message.relayId == 0) { + if (message.uplinkId == 0) { Serial.print(F("Ignoring message from ")); Serial.println(message.tubeId); } @@ -216,7 +189,7 @@ class Radio { Serial.print(F(" (relaying as ")); Serial.print(this->tubeId); Serial.print(F(")")); - message.relayId = message.tubeId; + message.reluplinkIdayId = message.tubeId; message.tubeId = this->tubeId; _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); } From 6436cbf663849d546348bc3485748fba209f5158 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 22 Jul 2022 05:19:24 -0700 Subject: [PATCH 115/263] Fix uplinkId following --- usermods/Tubes/bluetooth.h | 39 +++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 52217e1315..1784bc57a3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -152,6 +152,7 @@ static CharacteristicCallbacks chrCallbacks; class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { public: bool alive = false; // true if radio booted up + bool changed = false; MeshIds ids; byte buffer[100]; @@ -179,10 +180,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { return; auto service_data = std::string((char *)&ids, sizeof(ids)); - if (ids.uplinkId) - sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); - else - sprintf(node_name, "Tube %03X", ids.id); + sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); // // // Reset the device name // NimBLEDevice::deinit(false); @@ -235,7 +233,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->setAppearance(0x07C6); // Multi-color LED array pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_UND); - advertise(); + changed = true; } void init_scanner() { @@ -280,6 +278,11 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { follow(0); } + if (changed) { + advertise(); + changed = false; + } + if (!this->pScanner->isScanning()) { // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. this->pScanner->start(0, nullptr, false); @@ -317,25 +320,31 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void reset(MeshId id = 0) { if (id == 0) id = newMeshId(); - this->ids.id = id; - - Serial.printf("My ID is %03X", this->ids.id); - if (this->ids.id > this->ids.uplinkId) - this->ids.uplinkId = 0; + Serial.printf("My new ID is %03X", id); - advertise(); + this->ids.id = id; + MeshId uplinkId = this->ids.uplinkId; + if (id > uplinkId) + uplinkId = id; + follow(uplinkId); + changed = true; } void follow(MeshId uplinkId) { + if (uplinkId == 0) { + // Following zero means "follow yourself" + uplinkId = this->ids.id; + } + if (this->ids.uplinkId == uplinkId) return; this->ids.uplinkId = uplinkId; - advertise(); + changed = true; } bool is_following() { - return this->ids.uplinkId != 0; + return this->ids.uplinkId != 0 && this->ids.uplinkId != this->ids.id; } // ====== CALLBACKS ======= @@ -352,8 +361,8 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { memcpy(&data_ids, data.c_str(), data.length()); Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); - if (data_ids.id >= ids.uplinkId) { - follow(data_ids.id); + if (data_ids.uplinkId >= ids.uplinkId) { + follow(data_ids.uplinkId); uplink_ping(); } From 34fb83971c3c31c8e3b7116434c68447a0c8ad3d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 23 Jul 2022 00:00:03 -0700 Subject: [PATCH 116/263] Update status via mesh network --- usermods/Tubes/Tubes.h | 5 +- usermods/Tubes/bluetooth.h | 316 ++++++++++++++++++++++++++-------- usermods/Tubes/controller.h | 70 ++------ usermods/Tubes/debug.h | 25 +-- usermods/Tubes/global_state.h | 9 + usermods/Tubes/radio.h | 202 ---------------------- 6 files changed, 276 insertions(+), 351 deletions(-) delete mode 100644 usermods/Tubes/radio.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index d82375b659..303666c9bb 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -18,15 +18,13 @@ #include "led_strip.h" #include "controller.h" -#include "radio.h" #include "debug.h" class TubesUsermod : public Usermod { private: BeatController beats; - Radio radio; - PatternController controller = PatternController(MAX_REAL_LEDS, &beats, &radio); + PatternController controller = PatternController(MAX_REAL_LEDS, &beats); DebugController debug = DebugController(&controller); int* master = NULL; /* master.h deleted */ @@ -55,7 +53,6 @@ class TubesUsermod : public Usermod { beats.update(); controller.update(); debug.update(); - radio.update(); // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 1784bc57a3..e2a40d9355 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -2,25 +2,54 @@ #include #include +#include "global_state.h" +#define UPDATE_RATE 2000 // Rate at which uplink is queried for data +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +#define CURRENT_MESH_VERSION 1 +#define MAX_CONNECTED_CLIENTS 3 + +#define DATA_UPDATE_SERVICE "D00B" typedef uint16_t MeshId; typedef struct { - MeshId id = 0; - MeshId uplinkId = 0; -} MeshIds; + MeshId id = 0; + MeshId uplinkId = 0; + uint8_t version = CURRENT_MESH_VERSION; +} MeshNodeHeader; +typedef struct { + MeshNodeHeader header; + TubeState current; + TubeState next; +} MeshStorage; -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +// Asynchronous queue handling +typedef struct { + MeshId id; + NimBLEAddress address; +} MeshUpdateRequest; + +class MessageReceiver { + public: + + virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + +static TaskHandle_t xUpdaterTaskHandle; +QueueHandle_t UpdaterQueue = xQueueCreate(5, sizeof(MeshUpdateRequest)); +void procUpdaterTask(void* pvParameters); /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ class ServerCallbacks: public NimBLEServerCallbacks { void onConnect(NimBLEServer* pServer) { Serial.println("Client connected"); - Serial.println("Multi-connect support: start advertising"); - NimBLEDevice::startAdvertising(); + if (pServer->getConnectedCount() < MAX_CONNECTED_CLIENTS) + NimBLEDevice::startAdvertising(); }; /** Alternative onConnect() method to extract details of the connection. @@ -39,11 +68,6 @@ class ServerCallbacks: public NimBLEServerCallbacks { pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60); }; - void onDisconnect(NimBLEServer* pServer) { - Serial.println("Client disconnected - start advertising"); - NimBLEDevice::startAdvertising(); - }; - void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) { Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); }; @@ -87,6 +111,8 @@ class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { Serial.print(pCharacteristic->getUUID().toString().c_str()); Serial.print(": onWrite(), value: "); Serial.println(pCharacteristic->getValue().c_str()); + + ESP.restart(); }; /** Called before notification or indication is sent, @@ -149,25 +175,71 @@ static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks; +NimBLEClient* connectToServer(NimBLEAddress &peer_address) { + NimBLEClient* pClient = nullptr; + bool known_server = false; + + // Check if we have a client we should reuse + if (NimBLEDevice::getClientListSize()) { + // If we already know this peer, send false as the second argument in connect() + // to prevent refreshing the service database. This saves considerable time and power. + pClient = NimBLEDevice::getClientByPeerAddress(peer_address); + if (pClient) + known_server = true; + else + pClient = NimBLEDevice::getDisconnectedClient(); + + if (pClient) { + if (pClient->connect(peer_address, known_server)) + return pClient; + + // Failed to connect. Just drop the client rather than deleting it. + return NULL; + } + } + + // No client to reuse; create a new one - if we can. + if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { + Serial.println("Max clients reached - no more connections available"); + return NULL; + } + pClient = NimBLEDevice::createClient(); + + // Set up this client and attempt to connect. + pClient->setConnectionParams(12,12,0,51); + pClient->setConnectTimeout(5); + if (!pClient->connect(peer_address)) { + // Created a client but failed to connect, don't need to keep it as it has no data. + NimBLEDevice::deleteClient(pClient); + return NULL; + } + + return pClient; +} + + class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { public: bool alive = false; // true if radio booted up bool changed = false; - MeshIds ids; - byte buffer[100]; + MeshNodeHeader ids; char node_name[20]; uint16_t serviceUUID = 0xD00F; - NimBLEServer* pServer = NULL; - NimBLEService* pService = NULL; - NimBLEScan* pScanner = NULL; + NimBLEServer* pServer = nullptr; + NimBLEService* pService = nullptr; + NimBLEScan* pScanner = nullptr; + NimBLEAddress uplink_address; + + MessageReceiver *receiver = nullptr; Timer uplinkTimer; + Timer updateTimer; - MeshId newMeshId() { - return random(0, 4000); + BLEMeshNode(MessageReceiver *receiver) { + this->receiver = receiver; } void advertise() { @@ -182,7 +254,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { auto service_data = std::string((char *)&ids, sizeof(ids)); sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); - // // // Reset the device name + // Reset the device name: // NimBLEDevice::deinit(false); // NimBLEDevice::init(node_name); @@ -191,8 +263,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); pAdvertising->start(); - Serial.printf("Advertising %s", node_name); - Serial.println(); + Serial.printf("Advertising %s\n", node_name); } void init_service() { @@ -213,14 +284,20 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pServer = NimBLEDevice::createServer(); pServer->setCallbacks(new ServerCallbacks()); + pServer->advertiseOnDisconnect(true); - pService = pServer->createService("D00B"); + pService = pServer->createService(DATA_UPDATE_SERVICE); NimBLECharacteristic* pCharacteristic = pService->createCharacteristic( "FEED", - NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::BROADCAST + NIMBLE_PROPERTY::READ ); + pCharacteristic->setValue(""); + pCharacteristic->setCallbacks(&chrCallbacks); - pCharacteristic->setValue("Test"); + pCharacteristic = pService->createCharacteristic( + "F00D", + NIMBLE_PROPERTY::WRITE + ); pCharacteristic->setCallbacks(&chrCallbacks); /** Start the services when finished creating all Characteristics and Descriptors */ @@ -238,34 +315,67 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void init_scanner() { NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); - NimBLEDevice::setScanDuplicateCacheSize(200); + NimBLEDevice::setScanDuplicateCacheSize(20); pScanner = NimBLEDevice::getScan(); //create new scan // Set the callback for when devices are discovered, no duplicates. pScanner->setAdvertisedDeviceCallbacks(this, false); pScanner->setActiveScan(false); // Don't request data (it uses more energy) - pScanner->setInterval(200); // How often the scan occurs / switches channels; in milliseconds, - pScanner->setWindow(80); // How long to scan during the interval; in milliseconds. + pScanner->setInterval(97); // How often the scan occurs / switches channels; in milliseconds, + pScanner->setWindow(37); // How long to scan during the interval; in milliseconds. pScanner->setMaxResults(0); // do not store the scan results, use callback only. } + void init_updater() { + xTaskCreate( + procUpdaterTask, /* Function to implement the task */ + "UpdaterTask", /* Name of the task */ + 3840, /* Stack size in bytes */ + this, /* Task input parameter */ + tskIDLE_PRIORITY+1, /* Priority of the task */ + &xUpdaterTaskHandle /* Task handle. */ + ); + } + void init() { esp_wifi_set_ps(WIFI_PS_MIN_MODEM); esp_coex_preference_set(ESP_COEX_PREFER_BT); NimBLEDevice::init(std::string("Tube")); init_scanner(); init_service(); + init_updater(); this->alive = true; } - void uplink_ping() { - // Track the last time we received a message from our master - this->uplinkTimer.start(UPLINK_TIMEOUT); + void onPeerPing(MeshNodeHeader* pRemoteNode, NimBLEAdvertisedDevice* pAdvertisedDevice) { + Serial.printf("Found %03X/%03X at %s\n", + pRemoteNode->id, + pRemoteNode->uplinkId, + std::string(pAdvertisedDevice->getAddress()).c_str() + ); + + if (pRemoteNode->id == ids.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + if (pRemoteNode->id > ids.id && pRemoteNode->id > ids.uplinkId) { + follow(pRemoteNode->id, pAdvertisedDevice); + } + + if (pRemoteNode->id == ids.uplinkId) { + this->onUplinkAlive(); + } + } + + void setup() { + this->reset(); + Serial.println("Mesh: ok"); } void update() { - // Don't do anything for the first half-second to avoid crashing WiFi - if (millis() < 500) + // Don't do anything for the first second to avoid crashing WiFi + if (millis() < 1000) return; if (!this->alive) { @@ -278,6 +388,18 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { follow(0); } + if (this->ids.uplinkId && this->updateTimer.ended()) { + MeshUpdateRequest request = { + .id = this->ids.uplinkId, + .address = this->uplink_address + }; + if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { + Serial.println("Update queue is full!"); + } + updateTimer.snooze(UPDATE_RATE); + } + + // If any actions caused the service to change, re-advertise with new values if (changed) { advertise(); changed = false; @@ -289,20 +411,10 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } } - void broadcast(byte *data, int size) { - if (size > sizeof(buffer)) { - Serial.println("Too much data"); - return; - } - - memset(buffer, 0, sizeof(buffer)); - memcpy(buffer, data, size); - - if (!pServer) - return; - + void update_node_storage(TubeState ¤t, TubeState &next) { // Broadcast the current effect state to every connected client - if (pServer->getConnectedCount() == 0) + + if (!pServer || pServer->getConnectedCount() == 0) return; if (!pService) @@ -312,64 +424,116 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if(!pCharacteristic) return; - // Update the characteristic - pCharacteristic->setValue(buffer); - pCharacteristic->notify(true); + // Store this data in the characteristic + MeshStorage storage = { + .header = this->ids, + .current = current, + .next = next + }; + pCharacteristic->setValue(storage); } void reset(MeshId id = 0) { if (id == 0) - id = newMeshId(); - Serial.printf("My new ID is %03X", id); - + id = random(256, 4000); // Leave room at bottom and top of 12 bits this->ids.id = id; - MeshId uplinkId = this->ids.uplinkId; - if (id > uplinkId) - uplinkId = id; - follow(uplinkId); + follow(0); changed = true; } - void follow(MeshId uplinkId) { - if (uplinkId == 0) { - // Following zero means "follow yourself" - uplinkId = this->ids.id; - } + void follow(MeshId uplinkId, NimBLEAdvertisedDevice* pAdvertisedDevice = NULL) { + // Following zero means you have no uplink + // Update uplink device address + if (uplinkId && pAdvertisedDevice) + this->uplink_address = pAdvertisedDevice->getAddress(); + else + this->uplink_address = NimBLEAddress(); + + // Update uplink ID if (this->ids.uplinkId == uplinkId) return; - this->ids.uplinkId = uplinkId; changed = true; } bool is_following() { - return this->ids.uplinkId != 0 && this->ids.uplinkId != this->ids.id; + return this->ids.uplinkId != 0; } // ====== CALLBACKS ======= - void onResult(NimBLEAdvertisedDevice* advertisedDevice) { - if (!advertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) + void onResult(NimBLEAdvertisedDevice* pAdvertisedDevice) { + // Discovered a peer via scanning. + + if (!pAdvertisedDevice->isAdvertisingService(NimBLEUUID("D00B"))) return; - // Make sure it's booted up and advertising data - auto data = advertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); - if (!data.length()) + // Make sure it's booted up and advertising Mesh IDs + auto data = pAdvertisedDevice->getServiceData(NimBLEUUID(serviceUUID)); + if (data.length() != sizeof(MeshNodeHeader)) + return; + MeshNodeHeader* pRemoteNode = (MeshNodeHeader *)data.c_str(); + if (pRemoteNode->version != this->ids.version) return; - MeshIds data_ids; - memcpy(&data_ids, data.c_str(), data.length()); - Serial.printf("%03X/%03X ", data_ids.id, data_ids.uplinkId); + this->onPeerPing(pRemoteNode, pAdvertisedDevice); + } - if (data_ids.uplinkId >= ids.uplinkId) { - follow(data_ids.uplinkId); - uplink_ping(); + void onUpdateData(MeshUpdateRequest &request, MeshStorage &storage) { + if (request.id != storage.header.id) { + Serial.println("Uplink is invalid!"); + // The remote server has changed its ID. We need to adapt. + follow(0); + changed = true; + return; } + this->onUplinkAlive(); - Serial.printf("Found: %s: %s\n", - std::string(advertisedDevice->getAddress()).c_str(), - std::string(advertisedDevice->getName()).c_str() + // Process the command + this->receiver->onCommand( + storage.header.id, + COMMAND_UPDATE, + &storage.current + ); + this->receiver->onCommand( + storage.header.id, + COMMAND_NEXT, + &storage.next ); } + void onUplinkAlive() { + // Track the last time we received a message from our uplink + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + }; + + +// UPDATER +// This is an async task handler that awaits requests to update data, +// then connects to the requested server and fetches the data. +void procUpdaterTask(void* pvParameters) { + BLEMeshNode *pNode = (BLEMeshNode*)pvParameters; + MeshUpdateRequest request; + + for (;;) { + // Wait to be told to update (the queue blocks) + xQueueReceive(UpdaterQueue, &request, portMAX_DELAY); + + // Got a request to update, so try to connect and pull down data. + auto uplink_address = request.address; + auto pClient = connectToServer(uplink_address); + if (!pClient) + continue; + + auto pService = pClient->getService(DATA_UPDATE_SERVICE); + if (pService) { + auto pCharacteristic = pService->getCharacteristic("FEED"); + MeshStorage storage = pCharacteristic->readValue(); + pNode->onUpdateData(request, storage); + } + pClient->disconnect(); + } + +} \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8e0c2c435b..0ae346509a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -6,18 +6,11 @@ #include "palettes.h" #include "effects.h" #include "global_state.h" -#include "radio.h" +#include "bluetooth.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; +#define STATUS_UPDATE_PERIOD 1000 -const static CommandId COMMAND_HELLO = 0x00; -const static CommandId COMMAND_OPTIONS = 0x10; -const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_NEXT = 0x30; -const static CommandId COMMAND_RESET = 0xF0; - -const static CommandId COMMAND_BRIGHTNESS = 0x80; -const static CommandId COMMAND_FIREWORK = 0x90; typedef struct { @@ -80,8 +73,8 @@ class PatternController : public MessageReceiver { #endif LEDs *led_strip; BeatController *beats; - Radio *radio; Effects *effects; + BLEMeshNode *mesh; ControllerOptions options; char key_buffer[20] = {0}; @@ -90,15 +83,15 @@ class PatternController : public MessageReceiver { TubeState current_state; TubeState next_state; - PatternController(uint8_t num_leds, BeatController *beats, Radio *radio) { + PatternController(uint8_t num_leds, BeatController *beats) { this->num_leds = num_leds; #ifdef USELCD this->lcd = new Lcd(); #endif this->led_strip = new LEDs(num_leds); this->beats = beats; - this->radio = radio; this->effects = new Effects(); + this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED @@ -112,6 +105,7 @@ class PatternController : public MessageReceiver { void setup(bool isMaster) { + this->mesh->setup(); this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; @@ -125,16 +119,15 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; - Serial.println(F("Patterns: ok")); - - this->radio->setup(this->isMaster); - this->radio->sendCommand(COMMAND_HELLO); - this->updateTimer.start(RADIO_SENDPERIOD); // Ready to send an update as soon as we're able to + this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to + Serial.println("Patterns: ok"); } void update() { + this->mesh->update(); + this->read_keys(); // Update patterns to the beat @@ -157,10 +150,9 @@ class PatternController : public MessageReceiver { // Update current status if (this->updateTimer.ended()) { this->send_update(); + this->updateTimer.snooze(STATUS_UPDATE_PERIOD); } - this->radio->receiveCommands(this); - if (this->graphicsTimer.every(REFRESH_PERIOD)) { this->updateGraphics(); } @@ -212,8 +204,7 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - this->radio->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(this->current_state)); - this->updateTimer.snooze(RADIO_SENDPERIOD); + this->mesh->update_node_storage(this->current_state, this->next_state); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -225,7 +216,6 @@ class PatternController : public MessageReceiver { Serial.print(F("E: ")); this->next_state.print(); Serial.print(F(" ")); - this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); Serial.println(); } @@ -341,9 +331,11 @@ class PatternController : public MessageReceiver { } void optionsChanged() { +#ifdef NOT_COMPLETE if (this->isMaster) { this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } +#endif } void setBrightness(uint8_t brightness) { @@ -408,19 +400,12 @@ class PatternController : public MessageReceiver { addFlash(); } - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + virtual void onCommand(MeshId fromId, CommandId command, void *data) { if (fromId) { - Serial.print(F("From ")); - Serial.print(fromId); - Serial.print(F(": ")); + Serial.printf("From %03X: ", fromId); } switch (command) { - case COMMAND_FIREWORK: - Serial.print(F("fireworks")); - this->acknowledge(); - return; - case COMMAND_RESET: Serial.print(F("reset")); return; @@ -431,11 +416,6 @@ class PatternController : public MessageReceiver { return; } - case COMMAND_HELLO: - Serial.print(F("hello")); - this->updateTimer.stop(); - return; - case COMMAND_OPTIONS: { Serial.print(F("options")); memcpy(&this->options, data, sizeof(this->options)); @@ -522,16 +502,6 @@ class PatternController : public MessageReceiver { accum88 arg = this->parse_number(command+1); switch (command[0]) { - case 'f': - this->radio->sendCommandFrom(255, COMMAND_FIREWORK, NULL, 0); - this->onCommand(0, COMMAND_FIREWORK, NULL); - Serial.println(); - break; - - case 'i': - this->radio->mesh_node.reset(arg >> 8); - break; - case 'd': this->setDebugging(!this->options.debugging); break; @@ -609,12 +579,6 @@ class PatternController : public MessageReceiver { this->update_next(); return; - case 'h': - // Pretend to receive a HELLO - this->onCommand(0, COMMAND_HELLO, NULL); - Serial.println(); - return; - case 'g': for (int i=0; i< 10; i++) addGlitter(); @@ -645,7 +609,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->radio->sendCommand(COMMAND_NEXT, &this->next_state, sizeof(this->next_state)); + this->mesh->update_node_storage(this->current_state, this->next_state); } }; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 861ae393bd..a6e22109a4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,21 +1,20 @@ #pragma once #include "controller.h" -#include "radio.h" - +#include "bluetooth.h" class DebugController { public: PatternController *controller; LEDs *strip; - Radio *radio; + BLEMeshNode *mesh; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; this->strip = controller->led_strip; - this->radio = controller->radio; + this->mesh = controller->mesh; } void setup() @@ -27,14 +26,14 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("IP: %u.%u.%u.%u ", + Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d\n", + this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], - WiFi.localIP()[3] + WiFi.localIP()[3], + freeMemory() ); - Serial.print(F("Free memory: ")); - Serial.println( freeMemory() ); } // Show the beat on the master OR if debugging @@ -43,22 +42,16 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->radio->mesh_node.ids.id, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->mesh->ids.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->radio->mesh_node.ids.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->mesh->ids.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { this->strip->leds[p3] = CRGB::Yellow; } - if (this->radio->radioRestarts) { - this->strip->leds[1] = CRGB::Red; - } } - if (this->radio->radioFailures && !this->radio->radioRestarts) { - this->strip->leds[0] = CRGB::Red; - } } }; diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index a23a769aae..d75b940f83 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -52,3 +52,12 @@ class TubeState { } }; + +typedef uint8_t CommandId; + +const static CommandId COMMAND_OPTIONS = 0x10; +const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_NEXT = 0x30; +const static CommandId COMMAND_RESET = 0xF0; + +const static CommandId COMMAND_BRIGHTNESS = 0x80; diff --git a/usermods/Tubes/radio.h b/usermods/Tubes/radio.h deleted file mode 100644 index 3f44c41754..0000000000 --- a/usermods/Tubes/radio.h +++ /dev/null @@ -1,202 +0,0 @@ -#pragma once - -#include -#include - -#define RADIO_VERSION 1 - -#include "bluetooth.h" - -#define RADIO_SENDPERIOD 1000 // how often we broadcast, in millisec - -class Radio; - -typedef uint8_t CommandId; - -#define MESSAGE_DATA_MAX_SIZE 25 -typedef struct { - CommandId command; - byte data[MESSAGE_DATA_MAX_SIZE]; - uint16_t crc = 0; -} RadioMessage; - - -class MessageReceiver { - public: - - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } -}; - -uint16_t calculate_crc( byte *data, byte len ) { - - const unsigned long crc_table[16] = { - 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, - 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, - 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c - }; - - unsigned long crc = ~0L; - - for ( unsigned int index = 0 ; index < len ; ++index ) { - crc = crc_table[( crc ^ data[index] ) & 0x0f] ^ (crc >> 4); - crc = crc_table[( crc ^ ( data[index] >> 4 )) & 0x0f] ^ (crc >> 4); - crc = ~crc; - } - return crc & 65535; -} - - -void printMessageData(RadioMessage &message, int size) { - Serial.print(sizeof(message.data)); - Serial.print(F(":")); - for (unsigned int i = 0; i < sizeof(message.data); i++) { - if (message.data[i] < 16) - Serial.print(F("0")); - Serial.print(message.data[i], HEX); - Serial.print(F(" ")); - } - Serial.print(F("[")); - Serial.print(size); - Serial.print(F("] ")); -} - -class Radio { - public: - bool alive = false; // true if radio booted up - bool reported_no_radio = false; - - BLEMeshNode mesh_node = BLEMeshNode(); - - unsigned long radioFailures = 0; - unsigned long radioRestarts = 0; - - void setup(bool isMaster) { - if (isMaster) - mesh_node.reset(4500); - else - mesh_node.reset(); - - Serial.println(this->alive ? F("Radio: ok") : F("Radio: fail")); - // Start the radio, but mute & listen for a bit - } - - void update() { - mesh_node.update(); - } - - bool sendCommand(uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) - { - return this->sendCommandFrom(0, command, data, size, relayId); - } - - bool sendCommandFrom(MeshId id, uint32_t command, void *data=0, uint8_t size=0, MeshId relayId=0) - { - bool sent = false; - if (!this->alive) - return true; - - RadioMessage message; - if (size > sizeof(message.data)) { - Serial.println(F("Too big to send")); - return false; - } - - message.command = command + RADIO_VERSION; - memset(message.data, 0, sizeof(message.data)); - memcpy(message.data, data, size); - uint16_t crc = calculate_crc(message.data, sizeof(message.data)); - message.crc = crc; - - Serial.print(F("[")); - Serial.print(mesh_node.ids.id); - Serial.print(F(": ")); - Serial.print(message.command, HEX); - - mesh_node.broadcast((byte *)&message, sizeof(message)); - - Serial.print(sent ? F(" ok] ") : F(" failed] ")); - return true; - } - - void receiveCommands(MessageReceiver *receiver) - { -#ifdef USERADIO - RadioMessage message; - - if (!this->alive && !this->reported_no_radio) - { - Serial.println(F("No radio")); - this->reported_no_radio = true; - return; - } - - // check for incoming data - while (_radio.hasData()) - { - _radio.readData(&message); - - // Messages must be from a tube with the current version - if ((message.command>>12) != RADIO_VERSION) - return; - - // Ignore relayed messages if we already have a master - if (message.uplinkId && message.uplinkId <= this->uplinkTubeId) - return; - - // Filter out corrupt messages - unsigned long crc = calculate_crc(message.data, sizeof(message.data)); - if (crc != message.crc) { - // Corrupt packet... ignore it. - Serial.print(F("Invalid CRC: ")); - Serial.print(message.crc); - Serial.print(F(" should be ")); - Serial.println(crc); - continue; - } - - // If we detect an ID collision, fix it by choosing a new random one - while (message.tubeId == this->tubeId) { - Serial.print(F("ID collision!")); - this->resetId(); - } - - // Ignore messages from a lower ID - if (message.tubeId < this->tubeId) { - // Don't need to be noisy about relayed messages - if (message.uplinkId == 0) { - Serial.print(F("Ignoring message from ")); - Serial.println(message.tubeId); - } - return; - } - - if (message.tubeId != 255 && message.tubeId > this->uplinkTubeId) { - // Found a new master! - this->uplinkTubeId = message.tubeId; - Serial.print(F("My new uplink is ")); - Serial.println(this->uplinkTubeId); - } - - // Process the command - receiver->onCommand(message.tubeId, message.command & 0xFFF, message.data); - - // Occcasionally relay commands - more frequently if higher ID - uint8_t r = random8(); - if ((r % 3 == 0) && r < this->tubeId) { - Serial.print(F(" (relaying as ")); - Serial.print(this->tubeId); - Serial.print(F(")")); - message.reluplinkIdayId = message.tubeId; - message.tubeId = this->tubeId; - _radio.send(RADIO_TX_ID, &message, sizeof(message), NRFLite::NO_ACK); - } - - Serial.println(); - } -#endif - } - -}; From 7574f0340ab805dbff85b0dd08e27a67755b1460 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 23 Jul 2022 09:09:14 -0700 Subject: [PATCH 117/263] Fix controller upates; add better debugging --- usermods/Tubes/bluetooth.h | 6 ++++-- usermods/Tubes/controller.h | 9 +++++---- usermods/Tubes/debug.h | 24 ++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index e2a40d9355..4cc0f69e05 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -34,7 +34,7 @@ typedef struct { class MessageReceiver { public: - virtual void onCommand(uint8_t fromId, CommandId command, void *data) { + virtual void onCommand(MeshId fromId, CommandId command, void *data) { // Abstract: subclasses must define } }; @@ -370,6 +370,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { void setup() { this->reset(); + this->updateTimer.start(UPDATE_RATE); Serial.println("Mesh: ok"); } @@ -389,6 +390,8 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } if (this->ids.uplinkId && this->updateTimer.ended()) { + this->updateTimer.snooze(UPDATE_RATE); + MeshUpdateRequest request = { .id = this->ids.uplinkId, .address = this->uplink_address @@ -396,7 +399,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { Serial.println("Update queue is full!"); } - updateTimer.snooze(UPDATE_RATE); } // If any actions caused the service to change, re-advertise with new values diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 0ae346509a..ec9e4083af 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -407,17 +407,18 @@ class PatternController : public MessageReceiver { switch (command) { case COMMAND_RESET: - Serial.print(F("reset")); + Serial.println(F("reset")); return; case COMMAND_BRIGHTNESS: { uint8_t *bright = (uint8_t *)data; this->setBrightness(*bright); + Serial.println(); return; } case COMMAND_OPTIONS: { - Serial.print(F("options")); + Serial.println(F("options")); memcpy(&this->options, data, sizeof(this->options)); return; } @@ -427,7 +428,7 @@ class PatternController : public MessageReceiver { memcpy(&this->next_state, data, sizeof(TubeState)); this->next_state.print(); - Serial.print(F(" (obeying)")); + Serial.println(F(" (obeying)")); return; } @@ -437,7 +438,7 @@ class PatternController : public MessageReceiver { TubeState state; memcpy(&state, data, sizeof(TubeState)); state.print(); - Serial.print(F(" (obeying)")); + Serial.println(F(" (obeying)")); // Catch up to this state this->load_pattern(state); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index a6e22109a4..445feb508f 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -3,6 +3,24 @@ #include "controller.h" #include "bluetooth.h" +std::string formatted_time(long ms) { + long secs = ms / 1000; // set the seconds remaining + long mins = secs / 60; //convert seconds to minutes + long hours = mins / 60; //convert minutes to hours + long days = hours / 24; //convert hours to days + + secs = secs % 60; + mins = mins % 60; + hours = hours % 24; + + char buffer[100]; + if (days > 0) + sprintf(buffer, "%ld %02ld:%02ld:%02ld", days, hours, mins, secs); + else + sprintf(buffer, "%02ld:%02ld:%02ld", hours, mins, secs); + return std::string(buffer); +} + class DebugController { public: PatternController *controller; @@ -26,13 +44,14 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d\n", + Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n", this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3], - freeMemory() + freeMemory(), + formatted_time(millis()).c_str() ); } @@ -55,3 +74,4 @@ class DebugController { } }; + From e48c8ab8fd2d5a019380e820a272272af697b532 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 26 Jul 2022 00:54:48 -0700 Subject: [PATCH 118/263] easier to read debug --- usermods/Tubes/controller.h | 1 + usermods/Tubes/debug.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ec9e4083af..3669c25355 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -201,6 +201,7 @@ class PatternController : public MessageReceiver { } void send_update() { + Serial.print(" "); this->current_state.print(); Serial.print(F(" ")); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 445feb508f..8d47faa0b4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -44,7 +44,7 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("%s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n", + Serial.printf("\n=== %s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", this->controller->mesh->node_name, WiFi.localIP()[0], WiFi.localIP()[1], From 40c06dac24649ec9e582d19662a297164870dcae Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 26 Jul 2022 09:05:39 -0700 Subject: [PATCH 119/263] Add TimeSync - let's see if it works --- usermods/Tubes/TimeSync/counter.h | 409 +++++++++++++++++++++++++++++ usermods/Tubes/TimeSync/sync.h | 180 +++++++++++++ usermods/Tubes/TimeSync/timesync.h | 397 ++++++++++++++++++++++++++++ usermods/Tubes/bluetooth.h | 2 + usermods/Tubes/controller.h | 3 + usermods/Tubes/effects.h | 2 + usermods/Tubes/pattern.h | 9 + usermods/Tubes/virtual_strip.h | 2 + 8 files changed, 1004 insertions(+) create mode 100644 usermods/Tubes/TimeSync/counter.h create mode 100644 usermods/Tubes/TimeSync/sync.h create mode 100644 usermods/Tubes/TimeSync/timesync.h diff --git a/usermods/Tubes/TimeSync/counter.h b/usermods/Tubes/TimeSync/counter.h new file mode 100644 index 0000000000..a35187de1c --- /dev/null +++ b/usermods/Tubes/TimeSync/counter.h @@ -0,0 +1,409 @@ +/** \file + \brief Counter Math + \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Counter nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +/** \page Counter Math + + Represents an unsigned counter that can roll over from its maximum value + back to zero. A common example is a 32-bit timestamp from GetTickCount() + on Windows, which can roll-over and cause software bugs despite testing. + This also provides compression for counters. + + This class provides: + + Counters of 2 bits through 64 bits e.g. 24-bit counters + + Increment/decrement by 1 or a constant + + Safe comparison operator overloads + + Compression/decompression via truncation + + Unit tested software +*/ + +#include +#include + +// Compiler-specific force inline keyword +#if defined(_MSC_VER) + #define COUNTER_FORCE_INLINE inline __forceinline +#else // _MSC_VER + #define COUNTER_FORCE_INLINE inline __attribute__((always_inline)) +#endif // _MSC_VER + + +//------------------------------------------------------------------------------ +// Counter + +template class Counter +{ +public: + typedef Counter ThisType; + typedef T ValueType; + typedef typename std::make_signed::type SignedType; + + static const unsigned kBits = TkBits; ///< Number of data bits + static const T kMSB = (T)1 << (TkBits - 1); ///< Most Significant Bit + + /// Generate a bit mask with 1s for each data bit + /// The mask gets optimized away when compiler optimizations are enabled + static const T kMask = static_cast(-(int64_t)1) >> (sizeof(T) * 8 - kBits); + + /// Counter value + T Value; + + + //-------------------------------------------------------------------------- + // Assignment + + Counter(T value = 0) + : Value(value & kMask) + { + } + Counter(const ThisType& b) + : Value(b.Value) + { + } + + ThisType& operator=(T value) + { + Value = value & kMask; + return *this; + } + ThisType& operator=(const ThisType b) + { + Value = b.Value; + return *this; + } + + + //-------------------------------------------------------------------------- + // Accessors + + /// Get current value + COUNTER_FORCE_INLINE T ToUnsigned() const + { + return Value; + } + + + //-------------------------------------------------------------------------- + // Increment + + /// Pre-increment + COUNTER_FORCE_INLINE ThisType& operator++() + { + Value = (Value + 1) & kMask; + return *this; + } + + /// Pre-decrement + COUNTER_FORCE_INLINE ThisType& operator--() + { + Value = (Value - 1) & kMask; + return *this; + } + + /// Post-increment + COUNTER_FORCE_INLINE ThisType operator++(int) + { + const T oldValue = Value; + Value = (Value + 1) & kMask; + return oldValue; + } + + /// Post-decrement + COUNTER_FORCE_INLINE ThisType operator--(int) + { + const T oldValue = Value; + Value = (Value - 1) & kMask; + return oldValue; + } + + + //-------------------------------------------------------------------------- + // Addition + + COUNTER_FORCE_INLINE ThisType& operator+=(const ThisType b) + { + Value = (Value + b.Value) & kMask; + return *this; + } + + COUNTER_FORCE_INLINE ThisType operator+(const ThisType b) const + { + return Value + b.Value; // Implicit mask + } + + COUNTER_FORCE_INLINE ThisType& operator-=(const ThisType b) + { + Value = (Value - b.Value) & kMask; + return *this; + } + + COUNTER_FORCE_INLINE ThisType operator-(const ThisType b) const + { + return Value - b.Value; // Implicit mask + } + + + //-------------------------------------------------------------------------- + // Comparison + // We can only compare counters of the same type + + COUNTER_FORCE_INLINE bool operator==(const ThisType b) const + { + return Value == b.Value; + } + COUNTER_FORCE_INLINE bool operator!=(const ThisType b) const + { + return Value != b.Value; + } + COUNTER_FORCE_INLINE bool operator>=(const ThisType b) const + { + const T d = static_cast(Value - b.Value) & kMask; + return d < kMSB; + } + COUNTER_FORCE_INLINE bool operator<(const ThisType b) const + { + const T d = static_cast(Value - b.Value) & kMask; + return d >= kMSB; + } + COUNTER_FORCE_INLINE bool operator<=(const ThisType b) const + { + const T d = static_cast(b.Value - Value) & kMask; + return d < kMSB; + } + COUNTER_FORCE_INLINE bool operator>(const ThisType b) const + { + const T d = static_cast(b.Value - Value) & kMask; + return d >= kMSB; + } + + + //-------------------------------------------------------------------------- + // Counter Compression (Truncation) and Re-Expansion + + /** + These routines will truncate a counter to a smaller number of bits, + and later expand the smaller value back into the original counter value + provided with a reference counter. For example a 64-bit timestamp can + be compressed down to 24 bits, sent over a network, and then expanded + back to the original value given a local time at the receiver. + + This assumes that counters are counting up and that roll-over can only + happen one time. If a counter rolls over twice, then the resulting + expanded counter value will be incorrect. + */ + + /// Compress to smaller counter by truncating + template + COUNTER_FORCE_INLINE SmallerT Truncate() const + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + // Truncate to smaller type + return SmallerT(static_cast(Value)); + } + + /// Expand from truncated counter + /// Bias > 0 can be used to accept values farther in the past + /// Bias < 0 can be used to accept values farther in the future + template + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncatedWithBias( + const ThisType recent, + const SmallerT smaller, + const SignedType bias) + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + /** + The bits in the smaller counter were all truncated from the correct + value, so what needs to be determined now is all the higher bits. + + Examples: + + Recent Smaller => Expanded + ------ ------- -------- + 0x100 0xff 0x0ff + 0x16f 0x7f 0x17f + 0x17f 0x6f 0x16f + 0x1ff 0xa0 0x1a0 + 0x1ff 0x01 0x201 + + The choice to make is between -1, 0, +1 for the next bit position. + + Since we have no information about the high bits, it should be + sufficient to compare the recent low bits with the smaller value + in order to decide which one is correct: + + 00 - ff = -ff -> -1 + 6f - 7f = -10 -> 0 + 7f - 6f = +10 -> 0 + ff - a0 = +5f -> 0 + ff - 01 = +fe -> +1 + */ + + // First insert the low bits to get the default result + ThisType result = smaller.ToUnsigned() | (recent.ToUnsigned() & ~static_cast(SmallerT::kMask)); + + // Grab the low bits of the recent counter + const T recentLow = recent.ToUnsigned() & SmallerT::kMask; + + // If recent - smaller would be negative: + if (recentLow < smaller.ToUnsigned()) + { + // If it is large enough to roll back a MSB: + const T absDiff = smaller.ToUnsigned() - recentLow; + if (absDiff >= static_cast(SmallerT::kMSB - bias)) + result -= static_cast(SmallerT::kMSB) << 1; + } + else + { + // If it is large enough to roll ahead a MSB: + const T absDiff = recentLow - smaller.ToUnsigned(); + if (absDiff > static_cast(SmallerT::kMSB + bias)) + result += static_cast(SmallerT::kMSB) << 1; + } + + return result; + } + + /// Expand from truncated counter without any bias + template + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const SmallerT smaller) + { + static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); + + ValueType smallerMSB = smaller.Value & SmallerT::kMSB; + SignedType smallerSigned = smaller.Value - (smallerMSB << 1); + + auto smallRecent = static_cast(recent.Value & SmallerT::kMask); + + // Signed gap = partial - prev + auto gap = static_cast(smallerSigned - smallRecent) & SmallerT::kMask; + + ValueType gapMSB = gap & SmallerT::kMSB; + SignedType gapSigned = gap - (gapMSB << 1); + + // Result = recent + gap + return recent.Value + gapSigned; + } + + // Template specialization to optimize cases where the word size matches + // the field size. Otherwise the extra sign handling above is not elided + // by the compiler's optimizer: + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(32 < kBits, "Smaller type must be smaller"); + + const int32_t gap = static_cast(smaller.Value - static_cast(recent.Value)); + return recent + gap; + } + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(16 < kBits, "Smaller type must be smaller"); + + const int16_t gap = static_cast( smaller.Value - static_cast(recent.Value) ); + return recent + gap; + } + + static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( + const ThisType recent, + const Counter smaller) + { + static_assert(8 < kBits, "Smaller type must be smaller"); + + const int8_t gap = static_cast(smaller.Value - static_cast(recent.Value)); + return recent + gap; + } + + + static_assert(std::is_pod::value, "Type must be a plain-old data type"); + static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(TkBits > 0, "Invalid input"); + static_assert(sizeof(T) * 8 >= TkBits, "Base type is not wide enough"); + + static_assert(kMask != 0, "bugcheck"); + static_assert(kMSB < kMask, "bugcheck"); + static_assert(kMSB != 0, "bugcheck"); +}; + + +/// Convenience declarations +typedef Counter Counter64; +typedef Counter Counter56; +typedef Counter Counter48; +typedef Counter Counter40; +typedef Counter Counter32; +typedef Counter Counter24; +typedef Counter Counter16; +typedef Counter Counter10; +typedef Counter Counter8; +typedef Counter Counter4; + +static_assert(sizeof(Counter8) == 1, "Unexpected padding"); +static_assert(sizeof(Counter16) == 2, "Unexpected padding"); +static_assert(sizeof(Counter32) == 4, "Unexpected padding"); +static_assert(sizeof(Counter64) == 8, "Unexpected padding"); + +/** + CounterExpand() + + This is a common utility function that expands a 1-7 byte truncated + counter back into a 64-bit (8 byte) counter, based on the largest + counter value seen so far. + + Preconditions: bytes > 0 && bytes < 8 +*/ +COUNTER_FORCE_INLINE Counter64 CounterExpand( + uint64_t largest, + uint64_t partial, + unsigned bytes) +{ + switch (bytes) + { + case 1: return Counter64::ExpandFromTruncated(largest, Counter8((uint8_t)partial)); + case 2: return Counter64::ExpandFromTruncated(largest, Counter16((uint16_t)partial)); + case 3: return Counter64::ExpandFromTruncated(largest, Counter24((uint32_t)partial)); + case 4: return Counter64::ExpandFromTruncated(largest, Counter32((uint32_t)partial)); + case 5: return Counter64::ExpandFromTruncated(largest, Counter40(partial)); + case 6: return Counter64::ExpandFromTruncated(largest, Counter48(partial)); + case 7: return Counter64::ExpandFromTruncated(largest, Counter56(partial)); + default: + break; + } + + return 0; +} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/sync.h b/usermods/Tubes/TimeSync/sync.h new file mode 100644 index 0000000000..da8cb7234f --- /dev/null +++ b/usermods/Tubes/TimeSync/sync.h @@ -0,0 +1,180 @@ +/** \file + \brief TimeSync: Time Synchronization + \copyright Copyright (c) 2017-2019 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of TimeSync nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "timesync.h" + + +//------------------------------------------------------------------------------ +// WindowedMinTS24 + +void WindowedMinTS24::Update( + Counter24 value, + uint64_t timestamp, + const uint64_t windowLengthTime) +{ + const Sample sample(value, timestamp); + + // On the first sample, new best sample, or if window length has expired: + if (!IsValid() || + value <= Samples[0].Value || + Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + Reset(sample); + return; + } + + // Insert the new value into the sorted array + if (value <= Samples[1].Value) + Samples[2] = Samples[1] = sample; + else if (value <= Samples[2].Value) + Samples[2] = sample; + + // Expire best if it has been the best for a long time + if (Samples[0].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + // Also expire the next best if needed + if (Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime)) + { + Samples[0] = Samples[2]; + Samples[1] = sample; + } + else + { + Samples[0] = Samples[1]; + Samples[1] = Samples[2]; + } + Samples[2] = sample; + return; + } + + // Quarter of window has gone by without a better value - Use the second-best + if (Samples[1].Value == Samples[0].Value && + Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime / 4)) + { + Samples[2] = Samples[1] = sample; + return; + } + + // Half the window has gone by without a better value - Use the third-best one + if (Samples[2].Value == Samples[1].Value && + Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime / 2)) + { + Samples[2] = sample; + } +} + + +//------------------------------------------------------------------------------ +// TimeSynchronizer + +void TimeSynchronizer::OnPeerMinDeltaTS24(Counter24 minDeltaTS24) +{ + LastFC_MinDeltaTS24 = minDeltaTS24; + GotPeerUpdate = true; + + Recalculate(); +} + +unsigned TimeSynchronizer::OnAuthenticatedDatagramTimestamp( + Counter24 remoteSendTS24, + uint64_t localRecvUsec) +{ + const Counter24 localTS24 = (uint32_t)(localRecvUsec >> kTime23LostBits); + + // OWD_i + ClockDelta(L-R)_i = Local Receive Time - Remote Send Time + const Counter24 deltaTS24 = localTS24 - remoteSendTS24; + + WindowedMinTS24Deltas.Update(deltaTS24, localRecvUsec, kDriftWindowUsec); + + Recalculate(); + + // Estimated one-way-delay (OWD) for this datagram in microseconds. + // This does not include processing time only network delay and perhaps + // some delays from the Operating System when it is heavily loaded. + // Set to 0 if trip time is not available + unsigned networkTripUsec = 0; + + if (IsSynchronized()) + { + // This is equivalent to the shortest RTT/2 seen so far by any pair of packets, + // meaning that it is the average of the upstream and downstream OWD. + networkTripUsec = GetMinimumOneWayDelayUsec(); + + // While the OWD is an estimate, the relative delay between that + // smallest packet pair and the current datagram is actually precise: + const Counter24 minDeltaTS24 = GetMinDeltaTS24(); + if (deltaTS24 > minDeltaTS24) + { + const Counter24 relativeTS24 = deltaTS24 - minDeltaTS24; + networkTripUsec += relativeTS24.ToUnsigned() << kTime23LostBits; + } + + // What should happen here is if the delay of each packet varies a lot, then we should + // get pretty accurate OWD for each packet. But if the variance is low and the delays + // for upstream and downstream are asymmetric, then it will underestimate the OWD by + // half of that asymmetry. Hopefully this inaccuracy won't cause problems.. + } + + return networkTripUsec; +} + +void TimeSynchronizer::Recalculate() +{ + if (!WindowedMinTS24Deltas.IsValid() || !GotPeerUpdate) + return; + + // min(OWD_i) + ClockDelta(L-R)_i + const Counter24 minRecvDeltaTS24 = WindowedMinTS24Deltas.GetBest(); + + // min(OWD_j) + ClockDelta(R-L)_j + const Counter24 minSendDeltaTS24 = LastFC_MinDeltaTS24; + + // Assume min(OWD_i) = min(OWD_j): + // min(OWD) ~= (min(OWD_j) + min(OWD_i)) / 2 + const Counter23 minOWD_TS23 = (minSendDeltaTS24 + minRecvDeltaTS24).ToUnsigned() >> 1; + + // Assume ClockDelta(R-L)_j = -ClockDelta(L-R)_i: + // ClockDelta(R-L) ~= (ClockDelta(R-L)_j - ClockDelta(L-R)_i) / 2 + const Counter23 clockDelta_TS23 = (minSendDeltaTS24 - minRecvDeltaTS24).ToUnsigned() >> 1; + + // Calculate the time delta in microseconds + RemoteTimeDeltaUsec = clockDelta_TS23.ToUnsigned() << kTime23LostBits; + + // Calculate the minimum OWD, which may go negative and blow up.. + uint32_t min_owd_usec = minOWD_TS23.ToUnsigned() << kTime23LostBits; + + // If the implied subtraction went negative, correct to zero: + static const uint32_t kSignRolloverThreshold = (1 << 22) << kTime23LostBits; + if (min_owd_usec >= kSignRolloverThreshold) { + min_owd_usec = 0; + } + MinimumOneWayDelayUsec = min_owd_usec; + + Synchronized = true; +} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/timesync.h b/usermods/Tubes/TimeSync/timesync.h new file mode 100644 index 0000000000..48a04f5b31 --- /dev/null +++ b/usermods/Tubes/TimeSync/timesync.h @@ -0,0 +1,397 @@ +/** \file + \brief TimeSync: Time Synchronization + \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of TimeSync nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "counter.h" + +#include + +/** + Time Synchronization Protocol + + -- Motivation: + + Time synchronization is an important core component of an rUDP library, + enabling multiple advantages over reliable UDP libraries without: + + (1) This specific (new) time synchronization works better over cellular + networks than PTP/NTP. + + (2) The API provides time synchronization as a feature for applications, + enabling millisecond-accurate dead reckoning for video games, and + 16-microsecond-accurate timing for scientific applications with 2-3 bytes. + + (3) Peer2Peer NAT hole-punch can be optimized because it can use time + synchronization to initiate probes simultaneously on both peers. + + (4) Delay-based Congestion Control systems should use One Way Delay (OWD) + on each packet as a signal, which allows it to e.g. avoid causing latency + in realtime games while delivering a file transfer in the background. + All existing Delay-based CC algorithms use differential OWD rather than + proper time synchronization. By adding time synchronization, CC becomes + robust to changes in the base OWD as the end-points remain synced. + + -- Background: + + Network time synchronization can be done two ways: + (a) Broadcast - Infeasible on the Internet and so not used. + (b) Assuming that the link is symmetric, and trusting Min(RTT/2) = OWD. + Meaning that existing network time synchronization protocols like NTP and + PTP work by sending multiple probes, and then taking the probe with the + smallest round trip time to be the best data to use in the set of probes. + + Time synchronization at higher resolutions needs to be performed constantly + because clocks drift at a rate of about 1 millisecond per 10 seconds. + So, a common choice for reliable UDP game protocols is to probe for time + synchronization purposes (running NTP all the time) at a fixed interval of + e.g. 5 to 10 seconds. + + The disadvantage of all of these existing approaches is that they only use + a finite number of probes, and all probes may be slightly skewed, + especially when jitter is present, or cross-traffic, or self-congestion + from a file transfer. + + For cellular networks, existing time synchronization approaches provide + degraded results due to the 4-10 millisecond jitter on every packet. + "An End-to-End Measurement Study of Modern Cellular Data Networks" (2014) + https://www.comp.nus.edu.sg/~bleong/publications/pam14-ispcheck.pdf + + when a link is asymmetrical there is no known practical method for + performing time synchronization between two peers that meet on the Internet, + so in that case a best effort is done, and at least it will be consistent. + + -- Algorithm: + + This TimeSync protocol overcomes this jitter problem by using a massive + number of probes (every packet is a probe), greatly increasing the odds + of a minimal RTT probe. + + How it works is that every packet has a 3 byte microsecond timestamp on it, + large enough to prevent roll-over. Both sides record the receive time of + each packet as early as possible, and then throw the packet onto another + thread to process, so that the network delay is more accurate and each + client of a server can run in parallel. + + While each packet is being processed the difference in send and receive + times are compared with prior such differences. And a windowed minimum + of these differences is updated. Periodically this minimum difference + is reported to the remote peer so that both sides have both the minimum + (outgoing - incoming) difference and the minimum (incoming - outgoing) + difference. + + These minimum differences correspond to the shortest trips each way. + Effectively, it turns every single packet into a time synchronization + probe, guaranteeing that it gets the best result possible. + + -- The (Simple) Math for TimeSync: + + We measure (Smallest C2S Delta) and (Smallest S2C Delta) + through the per-packet timestamps. + + C2S = (Smallest C2S Delta) + = (Server Time @ Client Send Time) + (C2S Propagation Delay) - (Client Send Time) + = (C2S Propagation Delay) + (Clock Delta) + + S2C = (Smallest S2C Delta) + = (Client Time @ Server Send Time) + (S2C Propagation Delay) - (Server Send Time) + = (S2C Propagation Delay) - (Clock Delta) + + Such that (Propagation Delay) for each direction is minimized + independently as described previously. + + We want to solve for (Clock Delta) but there is a problem: + + Note that in the definition of C2S and S2C there are three unknowns and + only two measurements. To resolve this problem we make the assumption + that the min. propagation delays are almost the same in each direction. + + And so: + + (S2C Propagation Delay) approx equals (C2S Propagation Delay). + + Thus we can simply write: + + (Clock Delta) = (C2S - S2C) / 2 + + This gives us 23 bits of the delta between clocks, since the division + by 2 (right shift) pulls in an unknown high bit. The effect of any + link asymmetry is halved as a side effect, helping to minimize it. + + -- The (Simple) Math for Network Trip Time: + + It also provides a robust estimate of the "speed of light" between two + network hosts (minimal one-way delay). This information is then used to + calculate the network trip time for every packet that arrives: + + Let DD = Distance from the current packet timestamp difference and minimal. + DD = (Packet Receive - Packet Send) - Min(Packet Receive - Packet Send) + Packet trip time = (Minimal one-way delay) + DD. +*/ + + +//------------------------------------------------------------------------------ +// Constants + +/// Default One Way Delay (OWD) to return before time sync completes +static const uint32_t kDefaultOWDUsec = 200 * 1000; ///< 200 ms + +/// Number of bits removed from the low end of the microsecond timestamp +static const unsigned kTime23LostBits = 3; + +/// Largest 23-bit counter difference value considered positive +static const unsigned kTime23Bias = 0x200000; + +/// Error bound for 23-bit timestamps <= 8*2-1 = 15 microseconds +static const unsigned kTime23ErrorBound = (1 << kTime23LostBits) * 2 - 1; + +/// Number of bits removed from the low end of the microsecond timestamp +static const unsigned kTime16LostBits = 9; + +/// Largest 16-bit counter difference value considered positive +static const unsigned kTime16Bias = 0x4000; + +/// Error bound for 16-bit timestamps <= 512*2-1 = 1.023 milliseconds +static const unsigned kTime16ErrorBound = (1 << kTime16LostBits) * 2 - 1; + +/// Window size for WindowedMinTS24Deltas. +/// Since clocks drift over time, eventually old measurements must be ignored. +/// This is also the longest that a timing measurement will affect time synch. +/// Assumes that clocks drift 1 millisecond every 10 seconds +static const uint64_t kDriftWindowUsec = 10 * 1000 * 1000; ///< 10 seconds + + +//------------------------------------------------------------------------------ +// Types + +/// Use Counter23::Decompress to expand back to 64-bit counters +typedef Counter Counter23; + + +//------------------------------------------------------------------------------ +// WindowedMinTS24 + +/// Windowed minimum in TS24 units +class WindowedMinTS24 +{ +public: + WindowedMinTS24() {} + + struct Sample + { + /// Sample value + Counter24 Value; + + /// Timestamp of data collection + uint64_t Timestamp; + + + /// Default values and initializing constructor + explicit Sample(Counter24 value = 0, uint64_t timestamp = 0) + : Value(value) + , Timestamp(timestamp) + { + } + + /// Check if a timeout expired + inline bool TimeoutExpired(uint64_t now, uint64_t timeout) + { + return (uint64_t)(now - Timestamp) > timeout; + } + }; + + + /// Number of samples collected + static const unsigned kSampleCount = 3; + + /// Sorted samples from smallest to largest + Sample Samples[kSampleCount]; + + + /// Are there any samples? + inline bool IsValid() const + { + return Samples[0].Value != 0; ///< ish + } + + /// Get smallest sample + inline Counter24 GetBest() const + { + return Samples[0].Value; + } + + /// Reset samples + inline void Reset(const Sample sample = Sample()) + { + Samples[0] = Samples[1] = Samples[2] = sample; + } + + /// Update minimum with new value + void Update( + Counter24 value, + uint64_t timestamp, + const uint64_t windowLengthTime); +}; + + +//------------------------------------------------------------------------------ +// TimeSynchronizer + +class TimeSynchronizer +{ +public: + /** + OnPeerMinDeltaTS24() + + Call this when the peer provides its latest 24-bit MinDeltaTS24 value. + The peer should do this periodically, ideally faster in the first minute + and then settling down to once every 2 seconds or so. This can be used + as a keep-alive for example. + + minDeltaTS24: Provide the low 24 bits of the peer's delta value. + */ + void OnPeerMinDeltaTS24(Counter24 minDeltaTS24); + + /// Convert local time in microseconds to a 24-bit datagram timestamp + static inline uint32_t LocalTimeToDatagramTS24(uint64_t localUsec) + { + return (uint32_t)(localUsec >> kTime23LostBits) & 0x00ffffff; + } + + /** + OnAuthenticatedDatagramTimestamp() + + Call this when a datagram arrives with an attached 24-bit timestamp. + Ideally every UDP/IP datagram we receive will have a timestamp. + It is recommended to check if the datagram is from the peer before + accepting the timestamp on the datagram. + + remoteSendTS24: The 24-bit timestamp attached to an incoming datagram. + localRecvUsec: A recent timestamp in microsecond units. + + Returns estimated one way delay (OWD) in microseconds for this datagram. + Returns 0 if OWD is unavailable. + */ + unsigned OnAuthenticatedDatagramTimestamp( + Counter24 remoteSendTS24, + uint64_t localRecvUsec); + + + /// Get the minimum TS24 (receipt - send) delta seen in the past interval + inline Counter24 GetMinDeltaTS24() const + { + return WindowedMinTS24Deltas.GetBest(); + } + + /// Is time synchronized? + inline bool IsSynchronized() const + { + return Synchronized; + } + + /// Get the minimum one-way delay seen so far. + /// This is equivalent to the shortest RTT/2 seen so far by any pair of packets, + /// meaning that it is the average of the upstream and downstream OWD. + inline uint32_t GetMinimumOneWayDelayUsec() const + { + return MinimumOneWayDelayUsec; + } + + /// Returns 16-bit remote time field to send in a packet + inline uint16_t ToRemoteTime16(uint64_t localUsec) + { + if (!Synchronized) { + return 0; + } + + const uint16_t localTS16 = (uint16_t)(localUsec >> kTime16LostBits); + const uint16_t deltaTS16 = (uint16_t)(RemoteTimeDeltaUsec >> kTime16LostBits); + + return localTS16 + deltaTS16; + } + + /// Returns local time given local time from packet + inline uint64_t FromLocalTime16( + uint64_t localUsec, + Counter16 timestamp16) + { + return Counter64::ExpandFromTruncatedWithBias( + localUsec >> kTime16LostBits, + timestamp16, + kTime16Bias).ToUnsigned() << kTime16LostBits; + } + + /// Returns 23-bit remote time field to send in a packet + inline uint32_t ToRemoteTime23(uint64_t localUsec) + { + if (!Synchronized) { + return 0; + } + + const Counter23 localTS23 = (uint32_t)(localUsec >> kTime23LostBits); + const Counter23 deltaTS23 = RemoteTimeDeltaUsec >> kTime23LostBits; + + return (localTS23 + deltaTS23).ToUnsigned(); + } + + /// Returns local time given remote time from packet + inline uint64_t FromLocalTime23( + uint64_t localUsec, + Counter23 timestamp23) + { + return Counter64::ExpandFromTruncatedWithBias( + localUsec >> kTime23LostBits, + timestamp23, + kTime23Bias).ToUnsigned() << kTime23LostBits; + } + +protected: + /// Synchronized? + std::atomic Synchronized = ATOMIC_VAR_INIT(false); + + /// Calculated delta = (Remote time - Local time) + std::atomic RemoteTimeDeltaUsec = ATOMIC_VAR_INIT(0); ///< usec + + /// Calculated minimum OWD + std::atomic MinimumOneWayDelayUsec = ATOMIC_VAR_INIT(kDefaultOWDUsec); ///< in usec + + /// Windowed minimum value for received packet timestamp deltas + /// Keep track of the smallest (receipt - send) time delta seen so far + WindowedMinTS24 WindowedMinTS24Deltas; ///< in Timestamp24 units + + /// Keep a copy of the last MinDeltaUsec from the flow control data from peer + Counter24 LastFC_MinDeltaTS24 = 0; + + /// Is peer update received yet? + bool GotPeerUpdate = false; + + + /// Recalculate MinimumOneWayDelayUsec and RemoteTimeDeltaUsec + void Recalculate(); +}; \ No newline at end of file diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 4cc0f69e05..198e5c8fc3 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -4,6 +4,8 @@ #include #include "global_state.h" +#include "TimeSync/sync.h" + #define UPDATE_RATE 2000 // Rate at which uplink is queried for data #define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost #define CURRENT_MESH_VERSION 1 diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3669c25355..d557af6524 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -356,6 +356,9 @@ class PatternController : public MessageReceiver { } SyncMode randomSyncMode() { + #ifdef TESTING_PATTERNS + return All; + #endif uint8_t r = random8(128); if (r < 40) return SinDrift; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 245510aa75..147196c334 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -131,6 +131,7 @@ typedef struct { static const EffectDef gEffects[] = { {{None}, {LongDuration}}, +#ifndef TESTING_PATTERNS {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, @@ -150,5 +151,6 @@ static const EffectDef gEffects[] = { {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, +#endif }; const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 77fdf883ed..a5b2409196 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -79,6 +79,11 @@ void biwave(VirtualStrip *strip) } } +void tick(VirtualStrip *strip) { + strip->fill(CRGB::Black); + strip->leds[strip->beat % 16] = CRGB::White; +} + void sinelon(VirtualStrip *strip) { // a colored dot sweeping back and forth, with fading trails @@ -162,6 +167,9 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. PatternDef gPatterns[] = { +#ifdef TESTING_PATTERNS + {tick, LongDuration}, +#else {drawNoise, {ShortDuration}}, {drawNoise, {ShortDuration}}, {drawNoise, {MediumDuration}}, @@ -180,6 +188,7 @@ PatternDef gPatterns[] = { {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, {bpm_palette, {MediumDuration, HighEnergy}} +#endif }; /* diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 2312724709..66d8b97baa 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -7,6 +7,8 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 +#define TESTING_PATTERNS + class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); From a3e018fa1c05adf80f8fdd40bbae85782fc0dd9b Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 2 Aug 2022 22:01:40 -0700 Subject: [PATCH 120/263] Make the Tube mode use the current WLED palette --- usermods/Tubes/controller.h | 19 +++++++++++++++---- usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/virtual_strip.h | 19 ++++++++++++++++--- wled00/FX.h | 21 +++++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index d557af6524..8e4f220569 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1,5 +1,8 @@ #pragma once +#include "wled.h" +#include "FX.h" + #include "beats.h" #include "pattern.h" @@ -239,6 +242,8 @@ class PatternController : public MessageReceiver { this->background_changed(); } + // Choose the pattern to display at the next pattern cycle + // Return the number of phrases until the next pattern cycle uint16_t set_next_pattern(uint16_t phrase) { uint8_t pattern_id = random8(gPatternCount); PatternDef def = gPatterns[pattern_id]; @@ -260,23 +265,27 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) + if (this->current_state.palette_id == tube_state.palette_id) { + Serial.println("Nope, don't change"); return; + } this->current_state.palette_phrase = tube_state.palette_phrase; this->_load_palette(tube_state.palette_id); } void _load_palette(uint8_t palette_id) { - this->current_state.palette_id = palette_id % gGradientPaletteCount; + this->current_state.palette_id = palette_id; Serial.print(F("Change palette")); this->background_changed(); } + // Choose the palette to display at the next palette cycle + // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return random8(4,40); + return 1; // random8(4,40); } void load_effect(TubeState &tube_state) { @@ -299,6 +308,8 @@ class PatternController : public MessageReceiver { this->effects->load(this->current_state.effect_params); } + // Choose the effect to display at the next effect cycle + // Return the number of phrases until the next effect cycle uint16_t set_next_effect(uint16_t phrase) { uint8_t effect_num = random8(gEffectCount); @@ -320,7 +331,7 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - background.palette = gGradientPalettes[this->current_state.palette_id]; + background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; // re-use virtual strips to prevent heap fragmentation diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index ddbca2fd4b..58252d44d4 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -3,7 +3,7 @@ #define USE_WLED #include "wled.h" -#define MAX_REAL_LEDS 64 +#define MAX_REAL_LEDS 100 class LEDs { diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 66d8b97baa..e53bf82d05 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -3,11 +3,12 @@ #include "util.h" #include "options.h" #include "beats.h" +#include "palettes.h" #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -#define TESTING_PATTERNS +//#define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -15,7 +16,7 @@ typedef void (*BackgroundFn)(VirtualStrip *strip); class Background { public: BackgroundFn animate; - CRGBPalette16 palette; + uint8_t palette_id; SyncMode sync=All; }; @@ -72,6 +73,12 @@ class VirtualStrip { this->fader = 0; this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; + +#ifdef MASTER_TUBE + // Interface with WLED + WS2812FX::load_palette(background.palette_id); + stateChanged = true; +#endif } void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) @@ -157,7 +164,13 @@ class VirtualStrip { } CRGB palette_color(uint8_t c, uint8_t offset=0) { - return ColorFromPalette( this->background.palette, c + offset ); +#define WLED_COLORS +#ifdef WLED_COLORS + return WS2812FX::get_palette_crgb(c + offset); +#else + CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; + return ColorFromPalette( palette, c + offset ); +#endif } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { diff --git a/wled00/FX.h b/wled00/FX.h index e39d9aef2d..bcabd6f4ec 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -882,6 +882,27 @@ class WS2812FX { // 96 bytes void estimateCurrentAndLimitBri(void); + + CRGB external_buffer[EXTERNAL_BUFFER_SIZE]; // 4 bytes per element + + public: + static CRGB* get_external_buffer() { + return instance->external_buffer; + } + static CRGB get_palette_crgb(uint16_t c) { + uint32_t color = instance->color_from_palette(c, false, true, 255); + return instance->col_to_crgb(color); + } + static void load_palette(uint8_t palette_id) { + for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + WS2812FX::Segment& seg = instance->getSegment(i); + seg.palette = palette_id; + } + } + static WS2812FX* get_strip() { + return instance; + } + }; extern const char JSON_mode_names[]; From 75ccf2104fa74fc90860036d535c7fe68532e885 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 2 Aug 2022 23:01:26 -0700 Subject: [PATCH 121/263] Fix Tube mode --- usermods/Tubes/controller.h | 6 ++---- usermods/Tubes/pattern.h | 9 ++++----- usermods/Tubes/virtual_strip.h | 4 ++-- wled00/FX.h | 4 ++-- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8e4f220569..81fb462037 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -265,10 +265,8 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) { - Serial.println("Nope, don't change"); + if (this->current_state.palette_id == tube_state.palette_id) return; - } this->current_state.palette_phrase = tube_state.palette_phrase; this->_load_palette(tube_state.palette_id); @@ -285,7 +283,7 @@ class PatternController : public MessageReceiver { // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return 1; // random8(4,40); + return random8(4,40); } void load_effect(TubeState &tube_state) { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index a5b2409196..d9208c8a4a 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -97,8 +97,7 @@ void bpm_palette(VirtualStrip *strip) { uint8_t beat = strip->bpm_sin16(64, 255); for (int i = 0; i < strip->num_leds; i++) { - CRGB c = strip->palette_color(i*2, strip->hue); - nscale8x3(c.r, c.g, c.b, beat-strip->hue+(i*10)); + CRGB c = strip->palette_color(i*2, strip->hue, beat-strip->hue+(i*10)); strip->leds[i] = c; } } @@ -177,13 +176,13 @@ PatternDef gPatterns[] = { {drawNoise, {MediumDuration}}, {drawNoise, {LongDuration}}, {drawNoise, {LongDuration}}, - {rainbow, {ShortDuration}}, + // {rainbow, {ShortDuration}}, {confetti, {ShortDuration}}, {confetti, {MediumDuration}}, {juggle, {ShortDuration}}, - {bpm, {ShortDuration}}, - {bpm, {MediumDuration, HighEnergy}}, + // {bpm, {ShortDuration}}, + // {bpm, {MediumDuration, HighEnergy}}, {palette_wave, {ShortDuration}}, {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index e53bf82d05..bc58e13e10 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -163,10 +163,10 @@ class VirtualStrip { } } - CRGB palette_color(uint8_t c, uint8_t offset=0) { + CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { #define WLED_COLORS #ifdef WLED_COLORS - return WS2812FX::get_palette_crgb(c + offset); + return WS2812FX::get_palette_crgb(c + offset, brightness); #else CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; return ColorFromPalette( palette, c + offset ); diff --git a/wled00/FX.h b/wled00/FX.h index bcabd6f4ec..ecd6580870 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -889,8 +889,8 @@ class WS2812FX { // 96 bytes static CRGB* get_external_buffer() { return instance->external_buffer; } - static CRGB get_palette_crgb(uint16_t c) { - uint32_t color = instance->color_from_palette(c, false, true, 255); + static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { + uint32_t color = instance->color_from_palette(c, false, true, 255, brightness); return instance->col_to_crgb(color); } static void load_palette(uint8_t palette_id) { From acb4c7fbecfb1668847bda18e6c6f87684a074fa Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 3 Aug 2022 01:19:46 -0700 Subject: [PATCH 122/263] Fix palette error --- usermods/Tubes/controller.h | 1 - usermods/Tubes/pattern.h | 3 --- usermods/Tubes/virtual_strip.h | 1 + 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 81fb462037..70aec2ba6a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -292,7 +292,6 @@ class PatternController : public MessageReceiver { this->current_state.effect_params.chance == tube_state.effect_params.chance) return; - this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; this->_load_effect(tube_state.effect_params); } diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index d9208c8a4a..d8a17d13f0 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -176,13 +176,10 @@ PatternDef gPatterns[] = { {drawNoise, {MediumDuration}}, {drawNoise, {LongDuration}}, {drawNoise, {LongDuration}}, - // {rainbow, {ShortDuration}}, {confetti, {ShortDuration}}, {confetti, {MediumDuration}}, {juggle, {ShortDuration}}, - // {bpm, {ShortDuration}}, - // {bpm, {MediumDuration, HighEnergy}}, {palette_wave, {ShortDuration}}, {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index bc58e13e10..8622a72086 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -78,6 +78,7 @@ class VirtualStrip { // Interface with WLED WS2812FX::load_palette(background.palette_id); stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif } From 9c3c4f3d085c6cd34f4539d0fc1eb48f9331a2c6 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 03:40:28 -0400 Subject: [PATCH 123/263] Get QuickESP comms working --- usermods/Tubes/bluetooth.h | 84 ++++------------- usermods/Tubes/controller.h | 23 ++--- usermods/Tubes/debug.h | 16 ++-- usermods/Tubes/node.h | 183 ++++++++++++++++++++++++++++++++++++ usermods/Tubes/util.h | 13 ++- wled00/wled.cpp | 2 + wled00/wled.h | 2 +- 7 files changed, 231 insertions(+), 92 deletions(-) create mode 100644 usermods/Tubes/node.h diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 198e5c8fc3..4c11d89872 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -4,22 +4,14 @@ #include #include "global_state.h" -#include "TimeSync/sync.h" -#define UPDATE_RATE 2000 // Rate at which uplink is queried for data -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost -#define CURRENT_MESH_VERSION 1 -#define MAX_CONNECTED_CLIENTS 3 +#include "node.h" -#define DATA_UPDATE_SERVICE "D00B" +// #include "TimeSync/sync.h" -typedef uint16_t MeshId; +#define MAX_CONNECTED_CLIENTS 3 -typedef struct { - MeshId id = 0; - MeshId uplinkId = 0; - uint8_t version = CURRENT_MESH_VERSION; -} MeshNodeHeader; +#define DATA_UPDATE_SERVICE "D00B" typedef struct { MeshNodeHeader header; @@ -33,14 +25,6 @@ typedef struct { NimBLEAddress address; } MeshUpdateRequest; -class MessageReceiver { - public: - - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } -}; - static TaskHandle_t xUpdaterTaskHandle; QueueHandle_t UpdaterQueue = xQueueCreate(5, sizeof(MeshUpdateRequest)); void procUpdaterTask(void* pvParameters); @@ -226,7 +210,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { bool changed = false; MeshNodeHeader ids; - char node_name[20]; uint16_t serviceUUID = 0xD00F; @@ -237,14 +220,14 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { MessageReceiver *receiver = nullptr; - Timer uplinkTimer; - Timer updateTimer; - BLEMeshNode(MessageReceiver *receiver) { this->receiver = receiver; } void advertise() { + auto service_data = std::string((char *)&ids, sizeof(ids)); + +#ifdef BLE_MESH if (!pService) return; @@ -253,8 +236,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (!pAdvertising) return; - auto service_data = std::string((char *)&ids, sizeof(ids)); - sprintf(node_name, "Tube %03X:%03X", ids.id, ids.uplinkId); // Reset the device name: // NimBLEDevice::deinit(false); @@ -264,6 +245,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { pAdvertising->stop(); pAdvertising->setServiceData(NimBLEUUID(serviceUUID), service_data); pAdvertising->start(); +#endif Serial.printf("Advertising %s\n", node_name); } @@ -340,12 +322,17 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } void init() { + WiFi.mode(WIFI_AP_STA); + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + +#ifdef BLE_MESH esp_coex_preference_set(ESP_COEX_PREFER_BT); NimBLEDevice::init(std::string("Tube")); init_scanner(); init_service(); init_updater(); +#endif this->alive = true; } @@ -371,8 +358,6 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { } void setup() { - this->reset(); - this->updateTimer.start(UPDATE_RATE); Serial.println("Mesh: ok"); } @@ -385,15 +370,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { this->init(); } - // Check the last time we heard from the uplink node - if (is_following() && this->uplinkTimer.ended()) { - Serial.println("Uplink lost"); - follow(0); - } - - if (this->ids.uplinkId && this->updateTimer.ended()) { - this->updateTimer.snooze(UPDATE_RATE); - +#ifdef BLE_MESH MeshUpdateRequest request = { .id = this->ids.uplinkId, .address = this->uplink_address @@ -401,6 +378,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { if (xQueueSend(UpdaterQueue, &request, 0) != pdTRUE) { Serial.println("Update queue is full!"); } +#endif } // If any actions caused the service to change, re-advertise with new values @@ -409,13 +387,14 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { changed = false; } - if (!this->pScanner->isScanning()) { + if (this->pScanner && !this->pScanner->isScanning()) { // Start scan with: duration = 0 seconds(forever), no scan end callback, not a continuation of a previous scan. this->pScanner->start(0, nullptr, false); } } void update_node_storage(TubeState ¤t, TubeState &next) { +#ifdef BLE_MESH // Broadcast the current effect state to every connected client if (!pServer || pServer->getConnectedCount() == 0) @@ -435,34 +414,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { .next = next }; pCharacteristic->setValue(storage); - } - - void reset(MeshId id = 0) { - if (id == 0) - id = random(256, 4000); // Leave room at bottom and top of 12 bits - this->ids.id = id; - follow(0); - changed = true; - } - - void follow(MeshId uplinkId, NimBLEAdvertisedDevice* pAdvertisedDevice = NULL) { - // Following zero means you have no uplink - - // Update uplink device address - if (uplinkId && pAdvertisedDevice) - this->uplink_address = pAdvertisedDevice->getAddress(); - else - this->uplink_address = NimBLEAddress(); - - // Update uplink ID - if (this->ids.uplinkId == uplinkId) - return; - this->ids.uplinkId = uplinkId; - changed = true; - } - - bool is_following() { - return this->ids.uplinkId != 0; +#endif } // ====== CALLBACKS ======= diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 70aec2ba6a..b1fc151357 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -9,7 +9,7 @@ #include "palettes.h" #include "effects.h" #include "global_state.h" -#include "bluetooth.h" +#include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define STATUS_UPDATE_PERIOD 1000 @@ -21,8 +21,6 @@ typedef struct { uint8_t brightness; } ControllerOptions; -#define NEXT_PATTERN_TIME 53000 -#define NEXT_PALETTE_TIME 27000 #define NUM_VSTRIPS 3 @@ -77,7 +75,7 @@ class PatternController : public MessageReceiver { LEDs *led_strip; BeatController *beats; Effects *effects; - BLEMeshNode *mesh; + LightNode *node; ControllerOptions options; char key_buffer[20] = {0}; @@ -94,7 +92,8 @@ class PatternController : public MessageReceiver { this->led_strip = new LEDs(num_leds); this->beats = beats; this->effects = new Effects(); - this->mesh = new BLEMeshNode(this); + this->node = new LightNode(); + // this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED @@ -108,7 +107,7 @@ class PatternController : public MessageReceiver { void setup(bool isMaster) { - this->mesh->setup(); + this->node->setup(); this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; @@ -129,7 +128,7 @@ class PatternController : public MessageReceiver { void update() { - this->mesh->update(); + this->node->update(); this->read_keys(); @@ -208,7 +207,7 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - this->mesh->update_node_storage(this->current_state, this->next_state); + // this->node->update_node_storage(this->current_state, this->next_state); uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); @@ -622,13 +621,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->mesh->update_node_storage(this->current_state, this->next_state); + // this->node->update_node_storage(this->current_state, this->next_state); } }; - - - -// What's interesting? -// c53 - clouds -// m4 - swing drift diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 8d47faa0b4..28a8397526 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -1,7 +1,7 @@ #pragma once #include "controller.h" -#include "bluetooth.h" +#include "node.h" std::string formatted_time(long ms) { long secs = ms / 1000; // set the seconds remaining @@ -25,14 +25,14 @@ class DebugController { public: PatternController *controller; LEDs *strip; - BLEMeshNode *mesh; + LightNode *node; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; this->strip = controller->led_strip; - this->mesh = controller->mesh; + this->node = controller->node; } void setup() @@ -44,8 +44,10 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", - this->controller->mesh->node_name, + Serial.printf("\n=== %s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", + this->controller->node->node_name, + WiFi.status(), + WiFi.channel(), WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], @@ -61,10 +63,10 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->mesh->ids.id, this->strip->num_leds-1); + uint8_t p2 = scale8(this->controller->node->header.id, this->strip->num_leds-1); this->strip->leds[p2] = CRGB::White; - uint8_t p3 = scale8(this->controller->mesh->ids.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h new file mode 100644 index 0000000000..de7621322d --- /dev/null +++ b/usermods/Tubes/node.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#if defined ESP32 +#include +#include +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#else +#error "Unsupported platform" +#endif //ESP32 +#include + +#include "global_state.h" + +#define CURRENT_NODE_VERSION 1 +#define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS + +#define UPDATE_RATE 3000 // Rate at which uplink is queried for data +#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost + +typedef uint16_t MeshId; + +typedef struct { + MeshId id = 0; + MeshId uplinkId = 0; + uint8_t version = CURRENT_NODE_VERSION; +} MeshNodeHeader; + + +void onDataSent (uint8_t* address, uint8_t status) { + Serial.printf (">> Message sent to " MACSTR ", status: %d\n", MAC2STR (address), status); +} + +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.printf (">> Received %d bytes ", len); + Serial.printf ("\"%.*s\" ", len, data); + Serial.printf ("%s", broadcast ? "broadcast" : "unicast"); + Serial.printf ("@ %d dBm ", rssi); + Serial.printf ("from " MACSTR "\n" , MAC2STR(address)); +} + + +class MessageReceiver { + public: + + virtual void onCommand(MeshId fromId, CommandId command, void *data) { + // Abstract: subclasses must define + } +}; + + +#define NODE_STATUS_INIT 0 +#define NODE_STATUS_BROADCASTING 1 +#define NODE_STATUS_QUIET 2 + + +class LightNode { + public: + MeshNodeHeader header; + char node_name[20]; + + uint8_t status = NODE_STATUS_INIT; + bool statusChanged = false; + + Timer uplinkTimer; + Timer updateTimer; + + void onWifiConnect() { + if (this->status == NODE_STATUS_BROADCASTING) { + Serial.println("Stop Broadcasting"); + quickEspNow.stop(); + } + + Serial.println("Stop broadcasting"); + this->status = NODE_STATUS_QUIET; + } + + void onWifiDisconnect() { + if (this->status == NODE_STATUS_BROADCASTING) + return; + + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + + Serial.println("Broadcasting"); + this->status = NODE_STATUS_BROADCASTING; + } + + void onStatusChange() { + sprintf(this->node_name, + "Tube %03X:%03X", + this->header.id, + this->header.uplinkId + ); + } + + void onUplinkAlive() { + // Track the last time we received a message from our uplink + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + + void setup() { + this->reset(); + this->updateTimer.start(UPDATE_RATE); + this->status = NODE_STATUS_INIT; + this->onStatusChange(); + + quickEspNow.onDataRcvd(onDataReceived); + quickEspNow.onDataSent(onDataSent); + + Serial.println("Node: ok"); + } + + void broadcast() { + if (this->status != NODE_STATUS_BROADCASTING) { + Serial.println(">> BC NO"); + return; + } + + static unsigned int counter = 0; + static const String msg = "Hello! "; + + String message = String (msg) + " " + String (counter++); + // WiFi.disconnect (false, true); + if (!quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message.c_str (), message.length ())) { + Serial.printf (">>>>>>>>>> Message sent: %s\n", message.c_str ()); + } else { + Serial.printf (">>>>>>>>>> Message not sent\n"); + } + } + + void update() { + // Don't do anything for the first second, to allow Wifi to settle + if (millis() < 1000) + return; + + // Check the last time we heard from the uplink node + if (is_following() && this->uplinkTimer.ended()) { + Serial.println("Uplink lost"); + this->follow(0); + } + + if (this->updateTimer.ended()) { + if (WiFi.isConnected()) + this->onWifiConnect(); + else + this->onWifiDisconnect(); + + this->broadcast(); + this->updateTimer.snooze(UPDATE_RATE); + } + + if (this->statusChanged) { + this->onStatusChange(); + this->statusChanged = false; + } + } + + void reset(MeshId id = 0) { + if (id == 0) + id = random(256, 4000); // Leave room at bottom and top of 12 bits + this->header.id = id; + this->follow(0); + this->statusChanged = true; + } + + void follow(MeshId uplinkId) { + // Update uplink ID + if (this->header.uplinkId == uplinkId) + return; + + // Following zero means you have no uplink + this->header.uplinkId = uplinkId; + this->statusChanged = true; + } + + bool is_following() { + return this->header.uplinkId != 0; + } +}; \ No newline at end of file diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index 25cbc7a163..60371c52d5 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -2,6 +2,9 @@ #include "wled.h" +// Is this the tube that can control others? +#define MASTER_TUBE + uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { uint16_t rangewidth = highest - lowest; uint16_t scaledbeat = scale16( v, rangewidth ); @@ -11,6 +14,10 @@ uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { #define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) -#define __ESP32__ -#define USTD_OPTION_FS_FORCE_NO_FS -#include \ No newline at end of file +uint32_t freeMemory() { + return ESP.getFreeHeap(); +} + +// #define __ESP32__ +// #define USTD_OPTION_FS_FORCE_NO_FS +// #include \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 47c0ed02c7..f25516b2bf 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -483,6 +483,8 @@ void WLED::initAP(bool resetAP) } DEBUG_PRINT(F("Opening access point ")); DEBUG_PRINTLN(apSSID); + DEBUG_PRINT(F("Password ")); + DEBUG_PRINTLN(apPass); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); diff --git a/wled00/wled.h b/wled00/wled.h index 349a38b796..de9ec3f601 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -49,7 +49,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -//#define WLED_DEBUG +#define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS From 6509044cb6e96ed48ddf4ff4856cca4dffae79e6 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 04:00:24 -0400 Subject: [PATCH 124/263] auto-configure the network --- usermods/Tubes/node.h | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index de7621322d..f8ba49cf83 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -62,11 +62,18 @@ class LightNode { char node_name[20]; uint8_t status = NODE_STATUS_INIT; - bool statusChanged = false; + bool meshChanged = false; Timer uplinkTimer; Timer updateTimer; + void configure_ap() { + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + strcpy(apSSID, ""); + apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; + } + void onWifiConnect() { if (this->status == NODE_STATUS_BROADCASTING) { Serial.println("Stop Broadcasting"); @@ -89,7 +96,7 @@ class LightNode { this->status = NODE_STATUS_BROADCASTING; } - void onStatusChange() { + void onMeshChange() { sprintf(this->node_name, "Tube %03X:%03X", this->header.id, @@ -103,14 +110,17 @@ class LightNode { } void setup() { - this->reset(); + configure_ap(); + this->updateTimer.start(UPDATE_RATE); this->status = NODE_STATUS_INIT; - this->onStatusChange(); + + this->reset(); + this->onMeshChange(); quickEspNow.onDataRcvd(onDataReceived); quickEspNow.onDataSent(onDataSent); - + Serial.println("Node: ok"); } @@ -143,6 +153,11 @@ class LightNode { this->follow(0); } + if (this->meshChanged) { + this->onMeshChange(); + this->meshChanged = false; + } + if (this->updateTimer.ended()) { if (WiFi.isConnected()) this->onWifiConnect(); @@ -152,11 +167,6 @@ class LightNode { this->broadcast(); this->updateTimer.snooze(UPDATE_RATE); } - - if (this->statusChanged) { - this->onStatusChange(); - this->statusChanged = false; - } } void reset(MeshId id = 0) { @@ -164,7 +174,7 @@ class LightNode { id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; this->follow(0); - this->statusChanged = true; + this->meshChanged = true; } void follow(MeshId uplinkId) { @@ -174,7 +184,7 @@ class LightNode { // Following zero means you have no uplink this->header.uplinkId = uplinkId; - this->statusChanged = true; + this->meshChanged = true; } bool is_following() { From 81cdce1fa923fdd9f7a3b673e54be8f8237ac004 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 16:32:51 -0400 Subject: [PATCH 125/263] Get syncing working & Speed up color changes --- usermods/Tubes/controller.h | 14 ++-- usermods/Tubes/node.h | 151 ++++++++++++++++++++++++++---------- wled00/wled.h | 2 +- 3 files changed, 119 insertions(+), 48 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b1fc151357..a358bc1daa 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -14,6 +14,8 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define STATUS_UPDATE_PERIOD 1000 +#define MIN_COLOR_CHANGE_PHRASES 2 // 4 +#define MAX_COLOR_CHANGE_PHRASES 4 // 40 typedef struct { @@ -92,7 +94,7 @@ class PatternController : public MessageReceiver { this->led_strip = new LEDs(num_leds); this->beats = beats; this->effects = new Effects(); - this->node = new LightNode(); + this->node = new LightNode(this); // this->mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { @@ -128,7 +130,7 @@ class PatternController : public MessageReceiver { void update() { - this->node->update(); + this->node->update(this->current_state, this->next_state); this->read_keys(); @@ -282,7 +284,7 @@ class PatternController : public MessageReceiver { // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { this->next_state.palette_id = random8(gGradientPaletteCount); - return random8(4,40); + return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); } void load_effect(TubeState &tube_state) { @@ -411,10 +413,8 @@ class PatternController : public MessageReceiver { addFlash(); } - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - if (fromId) { - Serial.printf("From %03X: ", fromId); - } + virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { + Serial.printf("From %03X/%03X: ", header->id, header->uplinkId); switch (command) { case COMMAND_RESET: diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f8ba49cf83..139bf1407a 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -17,8 +17,9 @@ #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define UPDATE_RATE 3000 // Rate at which uplink is queried for data -#define UPLINK_TIMEOUT 10000 // Time at which uplink is presumed lost +#define BROADCAST_RATE 3000 // Rate at which to broadcast as leader +#define REBROADCAST_RATE 7000 // Rate at which to re-broadcast as follower +#define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost typedef uint16_t MeshId; @@ -28,26 +29,20 @@ typedef struct { uint8_t version = CURRENT_NODE_VERSION; } MeshNodeHeader; +typedef struct { + MeshNodeHeader header; + TubeState current; + TubeState next; +} NodeMessage; -void onDataSent (uint8_t* address, uint8_t status) { - Serial.printf (">> Message sent to " MACSTR ", status: %d\n", MAC2STR (address), status); -} - -void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - Serial.printf (">> Received %d bytes ", len); - Serial.printf ("\"%.*s\" ", len, data); - Serial.printf ("%s", broadcast ? "broadcast" : "unicast"); - Serial.printf ("@ %d dBm ", rssi); - Serial.printf ("from " MACSTR "\n" , MAC2STR(address)); -} +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); class MessageReceiver { public: - - virtual void onCommand(MeshId fromId, CommandId command, void *data) { - // Abstract: subclasses must define - } + virtual void onCommand(CommandId command, MeshNodeHeader* header, void *data) { + // Abstract: subclasses must define + } }; @@ -58,14 +53,24 @@ class MessageReceiver { class LightNode { public: + static LightNode* instance; + + MessageReceiver *receiver; MeshNodeHeader header; + char node_name[20]; uint8_t status = NODE_STATUS_INIT; bool meshChanged = false; Timer uplinkTimer; - Timer updateTimer; + Timer broadcastTimer; + + LightNode(MessageReceiver *receiver) { + LightNode::instance = this; + + this->receiver = receiver; + } void configure_ap() { strcpy(clientSSID, ""); @@ -104,45 +109,106 @@ class LightNode { ); } - void onUplinkAlive() { - // Track the last time we received a message from our uplink - this->uplinkTimer.start(UPLINK_TIMEOUT); + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + // Ignore this packet if it couldn't be a mesh report. + if (len != sizeof(NodeMessage)) + return; + + NodeMessage* message = (NodeMessage*)data; + // Serial.printf(">> Received %db ", len); + // Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + // Serial.printf("at " MACSTR, MAC2STR(address)); + // Serial.printf("@ %ddBm: ", rssi); + + // Ignore this packet if wrong version + if (message->header.version != this->header.version) { + // Serial.printf(" header.id <= this->header.uplinkId) { + // Serial.printf(" ignoring\n", rssi); + return; + } + + // Serial.printf(" listening\n", rssi); + this->onPeerPing(&message->header); + + // Execute the received command + MeshId fromId = message->header.uplinkId; + if (!fromId) fromId = message->header.id; + + this->receiver->onCommand( + COMMAND_UPDATE, + &message->header, + &message->current + ); + this->receiver->onCommand( + COMMAND_NEXT, + &message->header, + &message->next + ); + } + + void onPeerPing(MeshNodeHeader* node) { + if (node->id == this->header.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + if (node->id > this->header.uplinkId && node->id > this->header.id) { + Serial.printf("Following %03X:%03X\n", + node->id, + node->uplinkId + ); + + this->follow(node->id); + } + + if (node->id == this->header.uplinkId) { + this->uplinkTimer.start(UPLINK_TIMEOUT); + } } void setup() { configure_ap(); - this->updateTimer.start(UPDATE_RATE); + this->broadcastTimer.start(BROADCAST_RATE); this->status = NODE_STATUS_INIT; this->reset(); this->onMeshChange(); quickEspNow.onDataRcvd(onDataReceived); - quickEspNow.onDataSent(onDataSent); Serial.println("Node: ok"); } - void broadcast() { - if (this->status != NODE_STATUS_BROADCASTING) { - Serial.println(">> BC NO"); + void broadcast(TubeState ¤t, TubeState &next) { + // Don't broadcast if not in broadcast mode + if (this->status != NODE_STATUS_BROADCASTING) return; - } - static unsigned int counter = 0; - static const String msg = "Hello! "; - - String message = String (msg) + " " + String (counter++); - // WiFi.disconnect (false, true); - if (!quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message.c_str (), message.length ())) { - Serial.printf (">>>>>>>>>> Message sent: %s\n", message.c_str ()); + NodeMessage message = { + .header = this->header, + .current = current, + .next = next, + }; + + auto err = quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, + (uint8_t*)&message, sizeof(message)); + if (err) + Serial.printf(">> Broadcast error %d\n", err); + + if (this->is_following()) { + this->broadcastTimer.snooze(REBROADCAST_RATE); } else { - Serial.printf (">>>>>>>>>> Message not sent\n"); + this->broadcastTimer.snooze(BROADCAST_RATE); } } - void update() { + void update(TubeState ¤t, TubeState &next) { // Don't do anything for the first second, to allow Wifi to settle if (millis() < 1000) return; @@ -158,14 +224,13 @@ class LightNode { this->meshChanged = false; } - if (this->updateTimer.ended()) { + if (this->broadcastTimer.ended()) { if (WiFi.isConnected()) this->onWifiConnect(); else this->onWifiDisconnect(); - this->broadcast(); - this->updateTimer.snooze(UPDATE_RATE); + this->broadcast(current, next); } } @@ -190,4 +255,10 @@ class LightNode { bool is_following() { return this->header.uplinkId != 0; } -}; \ No newline at end of file +}; + +LightNode* LightNode::instance = nullptr; + +void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + LightNode::instance->onPeerData(address, data, len, rssi, broadcast); +} diff --git a/wled00/wled.h b/wled00/wled.h index de9ec3f601..12a39c5283 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -49,7 +49,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -#define WLED_DEBUG +// #define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS From 33fd411a084d22f29ca32cfa0603db88b0df5730 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 17:35:54 -0400 Subject: [PATCH 126/263] Restore tube tech & finish --- usermods/Tubes/virtual_strip.h | 3 ++- wled00/FX.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 8622a72086..5b12411e42 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -8,7 +8,7 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -//#define TESTING_PATTERNS +// #define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -77,6 +77,7 @@ class VirtualStrip { #ifdef MASTER_TUBE // Interface with WLED WS2812FX::load_palette(background.palette_id); + WS2812FX::load_pattern(FX_MODE_EXTERNAL); stateChanged = true; stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif diff --git a/wled00/FX.h b/wled00/FX.h index ecd6580870..b16efa2a57 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -899,6 +899,11 @@ class WS2812FX { // 96 bytes seg.palette = palette_id; } } + static void load_pattern(uint8_t pattern_id) { + for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + instance->setMode(i, pattern_id); + } + } static WS2812FX* get_strip() { return instance; } From 41959bb02d5c19c305d3cb72c4b01b55b06f8a66 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 6 Aug 2022 23:55:48 -0400 Subject: [PATCH 127/263] Turn keyboard commands back on. --- usermods/Tubes/controller.h | 9 +++++++-- wled00/wled_serial.cpp | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a358bc1daa..7cff1beab5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -594,7 +594,7 @@ class PatternController : public MessageReceiver { case 'g': for (int i=0; i< 10; i++) addGlitter(); - break; + return; case '?': Serial.println(F("b###.# - set bpm")); @@ -608,6 +608,11 @@ class PatternController : public MessageReceiver { Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); + return; + + default: + Serial.println("dunno?"); + return; } } @@ -621,7 +626,7 @@ class PatternController : public MessageReceiver { } void update_next() { - // this->node->update_node_storage(this->current_state, this->next_state); + this->node->broadcast(this->current_state, this->next_state); } }; diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index 2f45788711..d4ab88d7e7 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -69,6 +69,7 @@ void sendBytes(){ void handleSerial() { + return; if (pinManager.isPinAllocated(hardwareRX)) return; #ifdef WLED_ENABLE_ADALIGHT From 2df10567f745ba0a189877d8b7092afa9bb66131 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 7 Aug 2022 01:19:28 -0400 Subject: [PATCH 128/263] Move overlay effects to a Usermod overlay so they work on any pattern --- usermods/Tubes/Tubes.h | 7 ++++ usermods/Tubes/controller.h | 3 +- usermods/Tubes/effects.h | 4 +- usermods/Tubes/particle.h | 73 ++++++++++++++++++++----------------- wled00/FX.h | 6 +++ 5 files changed, 55 insertions(+), 38 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 303666c9bb..81aebeab47 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -57,6 +57,13 @@ class TubesUsermod : public Usermod { // Draw after everything else is done controller.led_strip->update(master != NULL); // ~25us } + + void handleOverlayDraw() + { + // Draw effects layers over whatever WLED is doing. + WS2812FX* leds = &strip; + controller.effects->draw(leds); + } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 7cff1beab5..bde3b66906 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -406,11 +406,10 @@ class PatternController : public MessageReceiver { } this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); - this->effects->draw(this->led_strip->leds, this->num_leds); } virtual void acknowledge() { - addFlash(); + addFlash(CRGB::Green); } virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 147196c334..47308197cd 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -112,11 +112,11 @@ class Effects { } } - void draw(CRGB strip[], uint8_t num_leds) { + void draw(WS2812FX* leds) { uint8_t len = numParticles; for (uint8_t i=0; idrawFn(particle, strip, num_leds); + particle->drawFn(particle, leds); } } diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d31e4185d7..bbe6118475 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -9,10 +9,10 @@ class Particle; -typedef void (*ParticleFn)(Particle *particle, CRGB strip[], uint8_t num_leds); +typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); -extern void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds); -extern void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds); +extern void drawPoint(Particle *particle, WS2812FX* leds); +extern void drawFlash(Particle *particle, WS2812FX* leds); class Particle { @@ -96,59 +96,62 @@ class Particle { return CRGB(r,g,b); } - void draw_with_pen(CRGB strip[], int pos, CRGB color) { + void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { CRGB new_color; - + switch (this->pen) { case Draw: - strip[pos] = color; + new_color = color; break; case Blend: - strip[pos] |= color; + new_color = WS2812FX::get_crgb(pos) | color; break; case Erase: - strip[pos] &= color; + new_color = WS2812FX::get_crgb(pos) & color; break; case Invert: - strip[pos] = -strip[pos]; + new_color = -WS2812FX::get_crgb(pos); break; case Brighten: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - strip[pos] += new_color; + new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); break; } case Darken: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - strip[pos] -= new_color; + new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); break; } case Flicker: { uint8_t t = color.getAverageLight(); - new_color = CRGB(t,t,t); - if (millis() % 2) - strip[pos] -= new_color; - else - strip[pos] += new_color; + if (millis() % 2) { + new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); + } else { + new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); + } break; } case White: - strip[pos] = CRGB::White; + new_color = CRGB::White; break; case Black: - strip[pos] = CRGB::Black; + new_color = CRGB::Black; break; + default: + // Unknown pen + return; } + + WS2812FX::set_crgb(pos, new_color); } }; @@ -182,55 +185,57 @@ void addParticle(Particle *particle) { particles[numParticles++] = particle; } -void drawFlash(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawFlash(Particle *particle, WS2812FX* leds) { + auto num_leds = leds->getLengthTotal(); uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); for (int pos = 0; pos < num_leds; pos++) { - particle->draw_with_pen(strip, pos, c); + particle->draw_with_pen(leds, pos, c); } } -void drawPoint(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawPoint(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); - particle->draw_with_pen(strip, pos, c); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); + particle->draw_with_pen(leds, pos, c); } -void drawRadius(Particle *particle, CRGB strip[], uint8_t num_leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { +void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; nscale8(&c, 1, bright); uint8_t y = pos - i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(strip, y, c); + particle->draw_with_pen(leds, y, c); if (i == 0) continue; y = pos + i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(strip, y, c); + particle->draw_with_pen(leds, y, c); } } -void drawPop(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawPop(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); uint8_t radius = scale16((sin16(age_frac/2) - 32768) * 2, 8); - drawRadius(particle, strip, num_leds, pos, radius, c); + drawRadius(particle, leds, pos, radius, c); } -void drawBeatbox(Particle *particle, CRGB strip[], uint8_t num_leds) { +void drawBeatbox(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, num_leds-1); + uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); uint8_t radius = 5; - drawRadius(particle, strip, num_leds, pos, radius, c, false); + drawRadius(particle, leds, pos, radius, c, false); } diff --git a/wled00/FX.h b/wled00/FX.h index b16efa2a57..23dbeb3d8e 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -907,6 +907,12 @@ class WS2812FX { // 96 bytes static WS2812FX* get_strip() { return instance; } + static CRGB get_crgb(uint32_t pos) { + return instance->col_to_crgb(instance->getPixelColor(pos)); + } + static void set_crgb(uint32_t pos, CRGB color) { + instance->setPixelColor(pos, instance->crgb_to_col(color)); + } }; From 39047cb57a98e960d993cabc85751aeb56f91779 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 8 Aug 2022 04:40:07 -0400 Subject: [PATCH 129/263] Update node mesh code for less chattiness and quicker startup --- usermods/Tubes/controller.h | 25 ++- usermods/Tubes/global_state.h | 1 - usermods/Tubes/node.h | 291 +++++++++++++++++++++------------- 3 files changed, 194 insertions(+), 123 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index bde3b66906..51db4d45f5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -412,9 +412,7 @@ class PatternController : public MessageReceiver { addFlash(CRGB::Green); } - virtual void onCommand(CommandId command, MeshNodeHeader *header, void *data) { - Serial.printf("From %03X/%03X: ", header->id, header->uplinkId); - + virtual void onCommand(CommandId command, void *data) { switch (command) { case COMMAND_RESET: Serial.println(F("reset")); @@ -433,22 +431,17 @@ class PatternController : public MessageReceiver { return; } - case COMMAND_NEXT: { - Serial.print(F(" next ")); - - memcpy(&this->next_state, data, sizeof(TubeState)); - this->next_state.print(); - Serial.println(F(" (obeying)")); - return; - } - case COMMAND_UPDATE: { Serial.print(F(" update ")); + auto update_data = (NodeUpdate*)data; + TubeState state; - memcpy(&state, data, sizeof(TubeState)); + memcpy(&state, &update_data->current, sizeof(TubeState)); + memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); state.print(); - Serial.println(F(" (obeying)")); + this->next_state.print(); + Serial.println(); // Catch up to this state this->load_pattern(state); @@ -460,7 +453,7 @@ class PatternController : public MessageReceiver { } Serial.print(F("UNKNOWN ")); - Serial.print(command, HEX); + Serial.println(command, HEX); } void read_keys() { @@ -625,7 +618,7 @@ class PatternController : public MessageReceiver { } void update_next() { - this->node->broadcast(this->current_state, this->next_state); + this->node->update_status(this->current_state, this->next_state); } }; diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index d75b940f83..bf4ba4f2e5 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,7 +57,6 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_NEXT = 0x30; const static CommandId COMMAND_RESET = 0xF0; const static CommandId COMMAND_BRIGHTNESS = 0x80; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 139bf1407a..f5d0d32309 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -14,12 +14,15 @@ #include "global_state.h" +// #define NODE_DEBUGGING +#define TESTING_NODE_ID 100 + #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define BROADCAST_RATE 3000 // Rate at which to broadcast as leader -#define REBROADCAST_RATE 7000 // Rate at which to re-broadcast as follower +#define BROADCAST_RATE 2000 // Rate at which to broadcast state updates as leader #define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost +#define REBROADCAST_TIME 15000 // Time at which followers are presumed re-uplinked typedef uint16_t MeshId; @@ -30,9 +33,16 @@ typedef struct { } MeshNodeHeader; typedef struct { - MeshNodeHeader header; TubeState current; TubeState next; +} NodeUpdate; + +typedef struct { + MeshNodeHeader header; + CommandId command; + union { + NodeUpdate update; + } data; } NodeMessage; void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); @@ -40,15 +50,16 @@ void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rs class MessageReceiver { public: - virtual void onCommand(CommandId command, MeshNodeHeader* header, void *data) { + virtual void onCommand(CommandId command, void *data) { // Abstract: subclasses must define } }; - -#define NODE_STATUS_INIT 0 -#define NODE_STATUS_BROADCASTING 1 -#define NODE_STATUS_QUIET 2 +typedef enum{ + NODE_STATUS_QUIET=0, + NODE_STATUS_STARTING=1, + NODE_STATUS_STARTED=2, +} NodeStatus; class LightNode { @@ -57,14 +68,15 @@ class LightNode { MessageReceiver *receiver; MeshNodeHeader header; + NodeStatus status = NODE_STATUS_QUIET; - char node_name[20]; + bool meshStarted = false; - uint8_t status = NODE_STATUS_INIT; - bool meshChanged = false; + char node_name[20]; - Timer uplinkTimer; - Timer broadcastTimer; + Timer uplinkTimer; // When this timer ends, assume uplink is lost. + Timer broadcastTimer; // When this timer ends, send a status update + Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink LightNode(MessageReceiver *receiver) { LightNode::instance = this; @@ -80,25 +92,26 @@ class LightNode { } void onWifiConnect() { - if (this->status == NODE_STATUS_BROADCASTING) { - Serial.println("Stop Broadcasting"); + if (this->meshStarted) { + Serial.println("WiFi connected: stop broadcasting"); quickEspNow.stop(); + this->meshStarted = false; } - - Serial.println("Stop broadcasting"); + this->status = NODE_STATUS_QUIET; } void onWifiDisconnect() { - if (this->status == NODE_STATUS_BROADCASTING) - return; - - WiFi.mode (WIFI_MODE_STA); - WiFi.disconnect(false, true); - quickEspNow.begin(1, WIFI_IF_STA); + if (!this->meshStarted) { + Serial.println("WiFi disconnected: start broadcasting"); + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + this->meshStarted = true; + } - Serial.println("Broadcasting"); - this->status = NODE_STATUS_BROADCASTING; + if (this->status == NODE_STATUS_QUIET) + this->status = NODE_STATUS_STARTING; } void onMeshChange() { @@ -109,147 +122,213 @@ class LightNode { ); } + void onPeerPing(MeshNodeHeader* node) { + // When receiving a message, if the IDs match, it's a conflict + // Reset to create a new ID. + if (node->id == this->header.id) { + Serial.println("Detected an ID conflict."); + this->reset(); + } + + // If the message arrives from a higher ID, switch into follower mode + if (node->id > this->header.uplinkId && node->id > this->header.id) { + if (this->header.id != TESTING_NODE_ID || node->id < 0x800) + this->follow(node); + } + + // If the message arrived from our uplink, track that we're still linked. + if (node->id == this->header.uplinkId) { + this->uplinkTimer.start(UPLINK_TIMEOUT); + } + + // If a message indicates that another node is following this one, + // enter or continue re-broadcasting mode (unless already LEAD) + if (node->uplinkId == this->header.id) { + Serial.printf("%03X/%03X is following me\n", node->id, node->uplinkId); + this->rebroadcastTimer.start(REBROADCAST_TIME); + } + } + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - // Ignore this packet if it couldn't be a mesh report. + // Ignore this message if it isn't a valid message payload. if (len != sizeof(NodeMessage)) return; NodeMessage* message = (NodeMessage*)data; - // Serial.printf(">> Received %db ", len); - // Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); - // Serial.printf("at " MACSTR, MAC2STR(address)); - // Serial.printf("@ %ddBm: ", rssi); - - // Ignore this packet if wrong version +#ifdef NODE_DEBUGGING + Serial.printf(">> Received %db ", len); + Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + Serial.printf("at " MACSTR, MAC2STR(address)); + Serial.printf("@ %ddBm: ", rssi); +#endif + + // Ignore this message if it's the wrong version. if (message->header.version != this->header.version) { - // Serial.printf(" header.id <= this->header.uplinkId) { - // Serial.printf(" ignoring\n", rssi); + // Track that another node exists, updating this node's understanding of the mesh. + this->onPeerPing(&message->header); + + // Ignore this message if not from my uplink + if (message->header.id != this->header.uplinkId) { +#ifdef NODE_DEBUGGING + Serial.printf(" ignoring\n"); +#endif return; } - // Serial.printf(" listening\n", rssi); - this->onPeerPing(&message->header); +#ifdef NODE_DEBUGGING + Serial.printf(" listening\n"); +#endif // Execute the received command - MeshId fromId = message->header.uplinkId; - if (!fromId) fromId = message->header.id; - - this->receiver->onCommand( - COMMAND_UPDATE, - &message->header, - &message->current - ); + Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); this->receiver->onCommand( - COMMAND_NEXT, - &message->header, - &message->next + message->command, + &message->data ); - } - void onPeerPing(MeshNodeHeader* node) { - if (node->id == this->header.id) { - Serial.println("Detected an ID conflict."); - this->reset(); - } - - if (node->id > this->header.uplinkId && node->id > this->header.id) { - Serial.printf("Following %03X:%03X\n", - node->id, - node->uplinkId - ); - - this->follow(node->id); + // Re-broadcast the message if appropriate + if (!this->rebroadcastTimer.ended()) { + message->header = this->header; + this->broadcast(message, true); } + } - if (node->id == this->header.uplinkId) { - this->uplinkTimer.start(UPLINK_TIMEOUT); - } + void broadcast(NodeMessage *message, bool is_rebroadcast=false) { + // Don't broadcast anything if this node isn't active. + if (this->status != NODE_STATUS_STARTED) + return; + + auto err = quickEspNow.send( + ESPNOW_BROADCAST_ADDRESS, + (uint8_t*)message, sizeof(*message) + ); + if (err) + Serial.printf(">> Broadcast error %d\n", err); } void setup() { configure_ap(); - this->broadcastTimer.start(BROADCAST_RATE); - this->status = NODE_STATUS_INIT; - +#ifdef NODE_DEBUGGING + this->reset(TESTING_NODE_ID); +#else this->reset(); - this->onMeshChange(); +#endif + this->broadcastTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); Serial.println("Node: ok"); } - void broadcast(TubeState ¤t, TubeState &next) { - // Don't broadcast if not in broadcast mode - if (this->status != NODE_STATUS_BROADCASTING) + void set_timer() { + // Timer in QUIET mode determines how often we'll check WiFi status + if (this->status == NODE_STATUS_QUIET) { + this->broadcastTimer.start(BROADCAST_RATE); + this->rebroadcastTimer.stop(); return; + } - NodeMessage message = { - .header = this->header, - .current = current, - .next = next, - }; - - auto err = quickEspNow.send (ESPNOW_BROADCAST_ADDRESS, - (uint8_t*)&message, sizeof(message)); - if (err) - Serial.printf(">> Broadcast error %d\n", err); + // Initial timer: wait for a bit before trying to broadcast. + // If this node's ID is high, it's more likely to be the leader, so wait less. + if (this->status == NODE_STATUS_STARTING) { + auto next_time = 4000 - this->header.id/2; + this->broadcastTimer.start(next_time); + this->rebroadcastTimer.start(REBROADCAST_TIME); + return; + } + // If following, only rebroadcast every 5 cycles if (this->is_following()) { - this->broadcastTimer.snooze(REBROADCAST_RATE); - } else { - this->broadcastTimer.snooze(BROADCAST_RATE); + auto next_time = 5 * BROADCAST_RATE; + + // Randomize a bit so not everyone is rebroadcasting at the same time + next_time += random(0, 2000) - 1000; + + this->broadcastTimer.start(next_time); + return; } + + this->broadcastTimer.start(BROADCAST_RATE); } void update(TubeState ¤t, TubeState &next) { - // Don't do anything for the first second, to allow Wifi to settle - if (millis() < 1000) - return; - // Check the last time we heard from the uplink node if (is_following() && this->uplinkTimer.ended()) { - Serial.println("Uplink lost"); - this->follow(0); - } - - if (this->meshChanged) { - this->onMeshChange(); - this->meshChanged = false; + this->follow(NULL); } if (this->broadcastTimer.ended()) { + // The broadcast timer doubles as a timer for startup delay + // Once the initial timer has ended, mark this node as started + if (this->status == NODE_STATUS_STARTING) + this->status = NODE_STATUS_STARTED; + + // Check WiFi status and update node status if wifi changed if (WiFi.isConnected()) this->onWifiConnect(); else this->onWifiDisconnect(); - this->broadcast(current, next); + // Set the next time and reset the rebroadcast monitoring + this->update_status(current, next); + this->set_timer(); } } + void update_status(TubeState ¤t, TubeState &next) { + // Broadcast (or rebroadcast) the current state + NodeMessage message = { + .header = this->header, + .command = COMMAND_UPDATE, + .data = { + .update = { + .current = current, + .next = next + } + } + }; + this->broadcast(&message); + } + void reset(MeshId id = 0) { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; - this->follow(0); - this->meshChanged = true; + this->status = NODE_STATUS_STARTING; + this->follow(NULL); + this->onMeshChange(); } - void follow(MeshId uplinkId) { - // Update uplink ID - if (this->header.uplinkId == uplinkId) + void follow(MeshNodeHeader* node) { + if (node == NULL) { + if (this->header.uplinkId != 0) { + Serial.println("Uplink lost"); + } + + // Unfollow: following zero means you have no uplink + this->header.uplinkId = 0; + this->onMeshChange(); + return; + } + + // Already following? ignore + if (this->header.uplinkId == node->id) return; - // Following zero means you have no uplink - this->header.uplinkId = uplinkId; - this->meshChanged = true; + // Follow + Serial.printf("Following %03X:%03X\n", + node->id, + node->uplinkId + ); + this->header.uplinkId = node->id; + this->onMeshChange(); } bool is_following() { From 4feab0c79459e748930bb3485748dbd16eeb000e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 9 Aug 2022 12:10:38 -0400 Subject: [PATCH 130/263] fix post merge --- usermods/Tubes/particle.h | 17 +++++++++-------- wled00/FX.cpp | 10 ++++++++-- wled00/FX.h | 13 ++++--------- wled00/const.h | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index bbe6118475..d1afb6a69d 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -97,6 +97,7 @@ class Particle { } void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { + CRGB c = CRGB(WS2812FX::get_strip()->getPixelColor(pos)); CRGB new_color; switch (this->pen) { @@ -105,35 +106,35 @@ class Particle { break; case Blend: - new_color = WS2812FX::get_crgb(pos) | color; + new_color = c | color; break; case Erase: - new_color = WS2812FX::get_crgb(pos) & color; + new_color = c & color; break; case Invert: - new_color = -WS2812FX::get_crgb(pos); + new_color = -c; break; case Brighten: { uint8_t t = color.getAverageLight(); - new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); + new_color = c + CRGB(t,t,t); break; } case Darken: { uint8_t t = color.getAverageLight(); - new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); + new_color = c - CRGB(t,t,t); break; } case Flicker: { uint8_t t = color.getAverageLight(); if (millis() % 2) { - new_color = WS2812FX::get_crgb(pos) - CRGB(t,t,t); + new_color = c - CRGB(t,t,t); } else { - new_color = WS2812FX::get_crgb(pos) + CRGB(t,t,t); + new_color = c + CRGB(t,t,t); } break; } @@ -151,7 +152,7 @@ class Particle { return; } - WS2812FX::set_crgb(pos, new_color); + WS2812FX::get_strip()->setPixelColor(pos, new_color); } }; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 189e3bd13c..90d8ffba90 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5284,7 +5284,7 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;!,!,!;!;2d"; -uint16_t WS2812FX::mode_external(void) { +uint16_t mode_external(void) { // uint8_t segment_id = strip.getMainSegmentId(); uint16_t length = strip.getLengthTotal(); @@ -5296,7 +5296,7 @@ uint16_t WS2812FX::mode_external(void) { strip.setPixelColor(i, external_buffer[p]); } } -static const char _data_FX_MODE_TUBES_NOISE[] PROGMEM = "External!@!,;!,!,;!;1d"; +static const char _data_FX_MODE_EXTERNAL[] PROGMEM = "External!@!,;!,!,;!;1d"; //////////////////////////////// // 2D Polar Lights // @@ -7531,6 +7531,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_DJLIGHT, &mode_DJLight, _data_FX_MODE_DJLIGHT); addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); +<<<<<<< HEAD } @@ -7575,3 +7576,8 @@ uint16_t WS2812FX::mode_tubes_moise(void) { return FRAMETIME; } +======= + //addEffect(FX_MODE_CUSTOMEFFECT, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT); //WLEDSR Custom Effects +#endif // USERMOD_AUDIOREACTIVE +} +>>>>>>> 0a7756d3 (fix post merge) diff --git a/wled00/FX.h b/wled00/FX.h index 23dbeb3d8e..a6b46246b9 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -890,12 +890,13 @@ class WS2812FX { // 96 bytes return instance->external_buffer; } static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { - uint32_t color = instance->color_from_palette(c, false, true, 255, brightness); - return instance->col_to_crgb(color); + Segment& segment = instance->getMainSegment(); + uint32_t color = segment.color_from_palette(c, false, true, 255, brightness); + return CRGB(color); } static void load_palette(uint8_t palette_id) { for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { - WS2812FX::Segment& seg = instance->getSegment(i); + Segment& seg = instance->getSegment(i); seg.palette = palette_id; } } @@ -907,12 +908,6 @@ class WS2812FX { // 96 bytes static WS2812FX* get_strip() { return instance; } - static CRGB get_crgb(uint32_t pos) { - return instance->col_to_crgb(instance->getPixelColor(pos)); - } - static void set_crgb(uint32_t pos, CRGB color) { - instance->setPixelColor(pos, instance->crgb_to_col(color)); - } }; diff --git a/wled00/const.h b/wled00/const.h index 3ab56fbdea..1f7963d6f8 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 58 +#define GRADIENT_PALETTE_COUNT 114 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" From 59b37a2683f42bb50772647b65ce20c02d81987f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 9 Aug 2022 12:36:52 -0400 Subject: [PATCH 131/263] more fix after merge --- wled00/FX.cpp | 4 +++- wled00/FX_fcn.cpp | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 90d8ffba90..9f2e798618 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5287,14 +5287,16 @@ static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fad uint16_t mode_external(void) { // uint8_t segment_id = strip.getMainSegmentId(); uint16_t length = strip.getLengthTotal(); + auto external_buffer = strip.get_external_buffer(); for (int i = 0, p = 0; i < length; i++, p++) { if (p >= EXTERNAL_BUFFER_SIZE) { p = 0; } - // strip.setPixelColor(i, color_from_palette(external_buffer[p], true, PALETTE_SOLID_WRAP, 0)); strip.setPixelColor(i, external_buffer[p]); } + + return FRAMETIME; } static const char _data_FX_MODE_EXTERNAL[] PROGMEM = "External!@!,;!,!,;!;1d"; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 21882d6527..1f64e2dfee 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1591,11 +1591,17 @@ uint8_t Bus::_gAWM = 255; const char JSON_mode_names[] PROGMEM = R"=====(["Mode names have moved"])====="; const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", -"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", -"Pastel","Sunset 2","Beach","Vintage","Departure","Landscape","Beech","Sherbet","Hult","Hult 64", -"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", -"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", -"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", -"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", -"Candy2" +"Forest","Rainbow","Rainbow Bands","July","Vintage 57","Vintage 01","Rivendell","RGI 15","Retro","Analogous", +"Pink Splash 08","Pink Splash 07","Coral Reef","Ocean Breeze 68","Ocean Breeze 36","Departure","Landscape 64","Landscape 33","Sherbet","Hult 65", +"Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Lava","Fire","Hiyane","Colorfull","Magenta Evening", +"Pink Purple","Sunset","Autumn","B/B/M/W","B/M/R","B/R/M/Y","Sunset Yellow","Cloud","Fire & Ice","BHW2", +"Rainfall","Angel","Butterfly","250K Meters","Midnight","Afterdusk","Blue Sky","Gold Orange","Frizell 05","Frizell 09", +"Frizell 10","Frizell 12","Fib 01","Fib 18","Fib 07","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a", +"Cyan Orange","C/W/G","Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple", +"Knoza 00","Knoza 18","Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konjo 18","Konjo 19", +"Konikyo","McCahon","Sulz 10","Sulz 12","Sulz 15","Sulz 21","Sulz 22","Pills","Pink/Yellow/Orange","Autumn 04", +"Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10","Landscape 76", +"Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10","Vintage 56", +"Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" ])====="; + From fc58969a5ecf35022aad9bdd2091104fddab75be Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 9 Aug 2022 14:10:20 -0400 Subject: [PATCH 132/263] More fixes after merge, update palettes --- usermods/Tubes/debug.h | 16 +++++++++++++++- usermods/Tubes/node.h | 4 ++-- wled00/FX_fcn.cpp | 17 ++++++++--------- wled00/const.h | 2 +- wled00/palettes.h | 13 +------------ 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 28a8397526..1d65231e9e 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -41,11 +41,25 @@ class DebugController { this->lastFrame = (uint32_t)-1; } + std::string status_code(NodeStatus status) { + switch (status) { + case NODE_STATUS_QUIET: + return std::string(" (quiet)"); + case NODE_STATUS_STARTING: + return std::string(" (starting)"); + case NODE_STATUS_STARTED: + return std::string(""); + default: + return std::string("??"); + } + } + void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", + Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", this->controller->node->node_name, + status_code(this->controller->node->status).c_str(), WiFi.status(), WiFi.channel(), WiFi.localIP()[0], diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f5d0d32309..cd5ca6a3d7 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -87,8 +87,8 @@ class LightNode { void configure_ap() { strcpy(clientSSID, ""); strcpy(clientPass, ""); - strcpy(apSSID, ""); - apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; + strcpy(apSSID, "mywled"); + apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; } void onWifiConnect() { diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 1f64e2dfee..e02e02ec7f 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1594,14 +1594,13 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "Forest","Rainbow","Rainbow Bands","July","Vintage 57","Vintage 01","Rivendell","RGI 15","Retro","Analogous", "Pink Splash 08","Pink Splash 07","Coral Reef","Ocean Breeze 68","Ocean Breeze 36","Departure","Landscape 64","Landscape 33","Sherbet","Hult 65", "Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Lava","Fire","Hiyane","Colorfull","Magenta Evening", -"Pink Purple","Sunset","Autumn","B/B/M/W","B/M/R","B/R/M/Y","Sunset Yellow","Cloud","Fire & Ice","BHW2", -"Rainfall","Angel","Butterfly","250K Meters","Midnight","Afterdusk","Blue Sky","Gold Orange","Frizell 05","Frizell 09", -"Frizell 10","Frizell 12","Fib 01","Fib 18","Fib 07","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a", -"Cyan Orange","C/W/G","Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple", -"Knoza 00","Knoza 18","Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konjo 18","Konjo 19", -"Konikyo","McCahon","Sulz 10","Sulz 12","Sulz 15","Sulz 21","Sulz 22","Pills","Pink/Yellow/Orange","Autumn 04", -"Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10","Landscape 76", -"Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10","Vintage 56", -"Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" +"Pink Purple","Sunset","Autumn","Blue/Magenta/White","Blue/Magenta/Red","Blue/Red/Yellow","Blue/Cyan/Yellow","Sunset Yellow","Cloud","Fire & Ice", +"BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", +"Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", +"Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18", +"Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", +"Autumn 04","Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10", +"Landscape 76","Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10", +"Vintage 56","Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" ])====="; diff --git a/wled00/const.h b/wled00/const.h index 1f7963d6f8..e345100c82 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 114 // custom palette.h +#define GRADIENT_PALETTE_COUNT 103 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" diff --git a/wled00/palettes.h b/wled00/palettes.h index 5bb34f13e7..c2ab9295bf 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -2019,14 +2019,10 @@ const byte* const gGradientPalettes[] PROGMEM = { Afterdusk_gp, BlueSky_gp, Gold_Orange_gp, - frizzell_05_gp, - frizzell_09_gp, frizzell_10_gp, frizzell_12_gp, - fib53_01_gp, fib53_18_gp, - fib53_07_gp, fib53_13_gp, fib53_17_gp, fib53_05_gp, @@ -2052,15 +2048,8 @@ const byte* const gGradientPalettes[] PROGMEM = { grindylow_15_gp, grindylow_21_gp, konjo_08_gp, - konjo_18_gp, - konjo_19_gp, konkikyo_19_gp, mccahon_16_gp, - sulz_10_gp, - sulz_12_gp, - sulz_15_gp, - sulz_21_gp, - sulz_22_gp, Pills_2_gp, Pink_Yellow_Orange_1_gp, es_autumn_04_gp, @@ -2153,7 +2142,7 @@ const byte* const gGradientPalettes[] PROGMEM = { */ }; const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); -#define GRADIENT_PALETTE_COUNT 114 +#define GRADIENT_PALETTE_COUNT 103 #endif From b757eae712cebe23ccebe20b23adb2946c0f1a7d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 00:50:50 -0400 Subject: [PATCH 133/263] Handle manual override of colors --- usermods/Tubes/controller.h | 19 ++++++++-- wled00/FX.h | 74 ++++++++++++++++++++++++++++++++++++- wled00/FX_fcn.cpp | 2 +- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 51db4d45f5..ea7a3dc342 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -130,14 +130,24 @@ class PatternController : public MessageReceiver { void update() { - this->node->update(this->current_state, this->next_state); - this->read_keys(); // Update patterns to the beat this->update_beat(); - uint16_t phrase = this->current_state.beat_frame >> 12; + + // Detect manual overrides & update the current state to match. + Segment& segment = WS2812FX::get_strip()->getMainSegment(); + if (segment.palette != this->current_state.palette_id) { + Serial.printf("Palette override = %d\n",segment.palette); + this->next_state.palette_id = segment.palette; + this->next_state.palette_phrase = phrase; + this->updateTimer.stop(); + } + // if (segment.mode != FX_MODE_EXTERNAL) { + // Serial.printf("Pattern override = %d\n",segment.mode); + // } + if (phrase >= this->next_state.pattern_phrase) { this->load_pattern(this->next_state); this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); @@ -151,6 +161,9 @@ class PatternController : public MessageReceiver { this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); } + // Update the mesh + this->node->update(this->current_state, this->next_state); + // Update current status if (this->updateTimer.ended()) { this->send_update(); diff --git a/wled00/FX.h b/wled00/FX.h index a6b46246b9..11c77a9b84 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -275,6 +275,7 @@ #define FX_MODE_2DMETABALLS 142 // non audio #define FX_MODE_2DPULSER 143 // non audio #define FX_MODE_2DDRIFT 144 // non audio +<<<<<<< HEAD #define FX_MODE_2DWAVERLY 145 // audio enhanced #define FX_MODE_2DSWIRL 146 // audio enhanced #define FX_MODE_2DAKEMI 147 // audio enhanced @@ -308,6 +309,68 @@ #define FX_MODE_ROCKTAVES 174 // audio enhanced #define MODE_COUNT 175 +======= +#endif +#ifndef WLED_DISABLE_AUDIO + #ifndef WLED_DISABLE_2D + #define FX_MODE_2DWAVERLY 145 // audio enhanced + #define FX_MODE_2DSWIRL 146 // audio enhanced + #define FX_MODE_2DAKEMI 147 // audio enhanced + // 148 & 149 reserved + #endif + #define FX_MODE_PIXELWAVE 150 // audio enhanced + #define FX_MODE_JUGGLES 151 // audio enhanced + #define FX_MODE_MATRIPIX 152 // audio enhanced + #define FX_MODE_GRAVIMETER 153 // audio enhanced + #define FX_MODE_PLASMOID 154 // audio enhanced + #define FX_MODE_PUDDLES 155 // audio enhanced + #define FX_MODE_MIDNOISE 156 // audio enhanced + #define FX_MODE_NOISEMETER 157 // audio enhanced + #define FX_MODE_NOISEFIRE 158 // audio enhanced + #define FX_MODE_PUDDLEPEAK 159 // audio enhanced + #define FX_MODE_RIPPLEPEAK 160 // audio enhanced + #define FX_MODE_GRAVCENTER 161 // audio enhanced + #define FX_MODE_GRAVCENTRIC 162 // audio enhanced +#endif + +#ifndef USERMOD_AUDIOREACTIVE + #ifndef WLED_DISABLE_AUDIO + #define MODE_COUNT 163 + #else + #ifndef WLED_DISABLE_2D + #define MODE_COUNT 145 + #else + #define MODE_COUNT 118 + #endif + #endif + +#else + + #ifdef WLED_DISABLE_AUDIO + #error Incompatible options: WLED_DISABLE_AUDIO and USERMOD_AUDIOREACTIVE + #endif + #ifdef WLED_DISABLE_2D + #error AUDIOREACTIVE usermod requires 2D support. + #endif + #define FX_MODE_2DGEQ 148 + #define FX_MODE_2DFUNKYPLANK 149 + #define FX_MODE_PIXELS 163 + #define FX_MODE_FREQWAVE 164 + #define FX_MODE_FREQMATRIX 165 + #define FX_MODE_WATERFALL 166 + #define FX_MODE_FREQPIXELS 167 + #define FX_MODE_BINMAP 168 + #define FX_MODE_NOISEMOVE 169 + #define FX_MODE_FREQMAP 170 + #define FX_MODE_GRAVFREQ 171 + #define FX_MODE_DJLIGHT 172 + #define FX_MODE_BLURZ 173 + #define FX_MODE_ROCKTAVES 174 + //#define FX_MODE_CUSTOMEFFECT 175 //WLEDSR Custom Effects + + #define MODE_COUNT 175 +#endif +>>>>>>> d4cc6df2 (Handle manual override of colors) typedef enum mapping1D2D { M12_Pixels = 0, @@ -895,13 +958,20 @@ class WS2812FX { // 96 bytes return CRGB(color); } static void load_palette(uint8_t palette_id) { - for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { Segment& seg = instance->getSegment(i); + if (!seg.isActive()) continue; + if (instance->paletteBlend) + seg.startTransition(instance->getTransition()); seg.palette = palette_id; } } static void load_pattern(uint8_t pattern_id) { - for (uint8_t i = 0; i < instance->getMaxSegments(); i++) { + for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { + Segment& seg = instance->getSegment(i); + if (!seg.isActive()) continue; + if (instance->paletteBlend) + seg.startTransition(instance->getTransition()); instance->setMode(i, pattern_id); } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e02e02ec7f..ea9966cf31 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1593,7 +1593,7 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", "Forest","Rainbow","Rainbow Bands","July","Vintage 57","Vintage 01","Rivendell","RGI 15","Retro","Analogous", "Pink Splash 08","Pink Splash 07","Coral Reef","Ocean Breeze 68","Ocean Breeze 36","Departure","Landscape 64","Landscape 33","Sherbet","Hult 65", -"Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Lava","Fire","Hiyane","Colorfull","Magenta Evening", +"Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Hot Lava","Fire","Hiyane","Colorfull","Magenta Evening", "Pink Purple","Sunset","Autumn","Blue/Magenta/White","Blue/Magenta/Red","Blue/Red/Yellow","Blue/Cyan/Yellow","Sunset Yellow","Cloud","Fire & Ice", "BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", "Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", From 6847c34eb0a78054865e57738aebfabe795d55bc Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 01:42:33 -0400 Subject: [PATCH 134/263] Rework mesh sync code to pull message sending up into controller --- usermods/Tubes/controller.h | 21 +++++---- usermods/Tubes/node.h | 91 ++++++++++++------------------------- 2 files changed, 42 insertions(+), 70 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ea7a3dc342..c900bf5f83 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -131,6 +131,9 @@ class PatternController : public MessageReceiver { void update() { this->read_keys(); + + // Update the mesh + this->node->update(); // Update patterns to the beat this->update_beat(); @@ -161,14 +164,14 @@ class PatternController : public MessageReceiver { this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); } - // Update the mesh - this->node->update(this->current_state, this->next_state); - // Update current status - if (this->updateTimer.ended()) { - this->send_update(); - this->updateTimer.snooze(STATUS_UPDATE_PERIOD); - } + if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { + // Transmit less often when following + if (!this->node->is_following() || random(0, 4) == 0) { + this->send_update(); + } + + } if (this->graphicsTimer.every(REFRESH_PERIOD)) { this->updateGraphics(); @@ -222,8 +225,6 @@ class PatternController : public MessageReceiver { this->current_state.print(); Serial.print(F(" ")); - // this->node->update_node_storage(this->current_state, this->next_state); - uint16_t phrase = this->current_state.beat_frame >> 12; Serial.print(F(" ")); Serial.print(this->next_state.pattern_phrase - phrase); @@ -235,6 +236,8 @@ class PatternController : public MessageReceiver { this->next_state.print(); Serial.print(F(" ")); Serial.println(); + + this->node->update_status(this->current_state, this->next_state); } void background_changed() { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index cd5ca6a3d7..0c863d072a 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -20,9 +20,9 @@ #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define BROADCAST_RATE 2000 // Rate at which to broadcast state updates as leader #define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost #define REBROADCAST_TIME 15000 // Time at which followers are presumed re-uplinked +#define WIFI_CHECK_RATE 2000 // Time at which we should check wifi status again typedef uint16_t MeshId; @@ -70,12 +70,10 @@ class LightNode { MeshNodeHeader header; NodeStatus status = NODE_STATUS_QUIET; - bool meshStarted = false; - char node_name[20]; + Timer statusTimer; // Use this timer to initialize and check wifi status Timer uplinkTimer; // When this timer ends, assume uplink is lost. - Timer broadcastTimer; // When this timer ends, send a status update Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink LightNode(MessageReceiver *receiver) { @@ -88,30 +86,30 @@ class LightNode { strcpy(clientSSID, ""); strcpy(clientPass, ""); strcpy(apSSID, "mywled"); + strcpy(apPass, "WledWled"); apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; } void onWifiConnect() { - if (this->meshStarted) { - Serial.println("WiFi connected: stop broadcasting"); - quickEspNow.stop(); - this->meshStarted = false; - } - + if (this->status == NODE_STATUS_QUIET) + return; + + Serial.println("WiFi connected: stop broadcasting"); + quickEspNow.stop(); this->status = NODE_STATUS_QUIET; + this->rebroadcastTimer.stop(); + this->statusTimer.start(WIFI_CHECK_RATE); } void onWifiDisconnect() { - if (!this->meshStarted) { - Serial.println("WiFi disconnected: start broadcasting"); - WiFi.mode (WIFI_MODE_STA); - WiFi.disconnect(false, true); - quickEspNow.begin(1, WIFI_IF_STA); - this->meshStarted = true; - } + if (this->status != NODE_STATUS_QUIET) + return; - if (this->status == NODE_STATUS_QUIET) - this->status = NODE_STATUS_STARTING; + Serial.println("WiFi disconnected: start broadcasting"); + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect(false, true); + quickEspNow.begin(1, WIFI_IF_STA); + this->initialize(); } void onMeshChange() { @@ -141,9 +139,11 @@ class LightNode { this->uplinkTimer.start(UPLINK_TIMEOUT); } - // If a message indicates that another node is following this one, - // enter or continue re-broadcasting mode (unless already LEAD) - if (node->uplinkId == this->header.id) { + // If a message indicates that another node is following this one, or + // should be (it's not following anything, but this node's ID is higher) + // enter or continue re-broadcasting mode. + if (node->uplinkId == this->header.id + || (node->uplinkId == 0 && node->id < this->header.id)) { Serial.printf("%03X/%03X is following me\n", node->id, node->uplinkId); this->rebroadcastTimer.start(REBROADCAST_TIME); } @@ -220,51 +220,26 @@ class LightNode { #else this->reset(); #endif - this->broadcastTimer.stop(); - + this->statusTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); - Serial.println("Node: ok"); } - void set_timer() { - // Timer in QUIET mode determines how often we'll check WiFi status - if (this->status == NODE_STATUS_QUIET) { - this->broadcastTimer.start(BROADCAST_RATE); - this->rebroadcastTimer.stop(); - return; - } - - // Initial timer: wait for a bit before trying to broadcast. + void initialize() { + // Initialization timer: wait for a bit before trying to broadcast. // If this node's ID is high, it's more likely to be the leader, so wait less. - if (this->status == NODE_STATUS_STARTING) { - auto next_time = 4000 - this->header.id/2; - this->broadcastTimer.start(next_time); - this->rebroadcastTimer.start(REBROADCAST_TIME); - return; - } - - // If following, only rebroadcast every 5 cycles - if (this->is_following()) { - auto next_time = 5 * BROADCAST_RATE; - - // Randomize a bit so not everyone is rebroadcasting at the same time - next_time += random(0, 2000) - 1000; - - this->broadcastTimer.start(next_time); - return; - } - - this->broadcastTimer.start(BROADCAST_RATE); + this->status = NODE_STATUS_STARTING; + this->statusTimer.start(3000 - this->header.id / 2); + this->rebroadcastTimer.stop(); } - void update(TubeState ¤t, TubeState &next) { + void update() { // Check the last time we heard from the uplink node if (is_following() && this->uplinkTimer.ended()) { this->follow(NULL); } - if (this->broadcastTimer.ended()) { + if (this->statusTimer.every(WIFI_CHECK_RATE)) { // The broadcast timer doubles as a timer for startup delay // Once the initial timer has ended, mark this node as started if (this->status == NODE_STATUS_STARTING) @@ -275,10 +250,6 @@ class LightNode { this->onWifiConnect(); else this->onWifiDisconnect(); - - // Set the next time and reset the rebroadcast monitoring - this->update_status(current, next); - this->set_timer(); } } @@ -301,9 +272,7 @@ class LightNode { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits this->header.id = id; - this->status = NODE_STATUS_STARTING; this->follow(NULL); - this->onMeshChange(); } void follow(MeshNodeHeader* node) { From 738eaa16e6e68d84f994a4455db9cb30363d4b1a Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 03:21:19 -0400 Subject: [PATCH 135/263] misc utils --- usermods/Tubes/Tubes.h | 1 + usermods/Tubes/debug.h | 4 +++- usermods/Tubes/node.h | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 81aebeab47..ff12ae99fc 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -30,6 +30,7 @@ class TubesUsermod : public Usermod { void randomize() { randomSeed(esp_random()); + random16_set_seed(random(0, 65535)); random16_add_entropy(esp_random()); } diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 1d65231e9e..9062358ee6 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -57,7 +57,7 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d Uptime: %s\n\n", + Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n\n", this->controller->node->node_name, status_code(this->controller->node->status).c_str(), WiFi.status(), @@ -67,6 +67,8 @@ class DebugController { WiFi.localIP()[2], WiFi.localIP()[3], freeMemory(), + LITTLEFS.usedBytes(), + LITTLEFS.totalBytes(), formatted_time(millis()).c_str() ); } diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 0c863d072a..c87a697a1e 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -85,8 +85,9 @@ class LightNode { void configure_ap() { strcpy(clientSSID, ""); strcpy(clientPass, ""); - strcpy(apSSID, "mywled"); + sprintf(apSSID, "WLED %03X", this->header.id); strcpy(apPass, "WledWled"); + apActive = !this->is_following(); apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; } @@ -118,6 +119,7 @@ class LightNode { this->header.id, this->header.uplinkId ); + this->configure_ap(); } void onPeerPing(MeshNodeHeader* node) { From db66b5898b00a730f31b7d9a5858f973218a7335 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 12:20:57 -0400 Subject: [PATCH 136/263] Fix palette fading --- usermods/Tubes/controller.h | 42 ++++++++++++++++++++++--------------- usermods/Tubes/node.h | 15 ++++++++++--- wled00/FX.h | 9 ++++---- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c900bf5f83..60eabb4018 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -12,7 +12,7 @@ #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -#define STATUS_UPDATE_PERIOD 1000 +#define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 @@ -126,6 +126,25 @@ class PatternController : public MessageReceiver { this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); + + WS2812FX::load_pattern(FX_MODE_EXTERNAL); + } + + void do_pattern_changes() { + uint16_t phrase = this->current_state.beat_frame >> 12; + + if (phrase >= this->next_state.pattern_phrase) { + this->load_pattern(this->next_state); + this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + } + if (phrase >= this->next_state.palette_phrase) { + this->load_palette(this->next_state); + this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + } + if (phrase >= this->next_state.effect_phrase) { + this->load_effect(this->next_state); + this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + } } void update() @@ -151,32 +170,21 @@ class PatternController : public MessageReceiver { // Serial.printf("Pattern override = %d\n",segment.mode); // } - if (phrase >= this->next_state.pattern_phrase) { - this->load_pattern(this->next_state); - this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); - } - if (phrase >= this->next_state.palette_phrase) { - this->load_palette(this->next_state); - this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); - } - if (phrase >= this->next_state.effect_phrase) { - this->load_effect(this->next_state); - this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + do_pattern_changes(); + + if (this->graphicsTimer.every(REFRESH_PERIOD)) { + this->updateGraphics(); } // Update current status if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!this->node->is_following() || random(0, 4) == 0) { + if (!this->node->is_following() || random(0, 5) == 0) { this->send_update(); } } - if (this->graphicsTimer.every(REFRESH_PERIOD)) { - this->updateGraphics(); - } - #ifdef USELCD if (this->lcd->active) { this->lcd->size(1); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index c87a697a1e..58f3f40b87 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -13,6 +13,7 @@ #include #include "global_state.h" +#include "wled.h" // #define NODE_DEBUGGING #define TESTING_NODE_ID 100 @@ -83,12 +84,20 @@ class LightNode { } void configure_ap() { + // Try to hide the access point unless this is the "root" node strcpy(clientSSID, ""); strcpy(clientPass, ""); - sprintf(apSSID, "WLED %03X", this->header.id); + if (this->is_following()) { + sprintf(apSSID, "WLED %03X", this->header.id); + } else { + sprintf(apSSID, "WLED %03X", this->header.id); + } strcpy(apPass, "WledWled"); - apActive = !this->is_following(); - apBehavior = AP_BEHAVIOR_NO_CONN; // AP_BEHAVIOR_BOOT_NO_CONN; + apBehavior = AP_BEHAVIOR_NO_CONN; + + bootPreset = 0; // Try to prevent initial playlists from starting + fadeTransition = true; + transitionDelay = 6000; } void onWifiConnect() { diff --git a/wled00/FX.h b/wled00/FX.h index 11c77a9b84..fad540d9d2 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -960,9 +960,9 @@ class WS2812FX { // 96 bytes static void load_palette(uint8_t palette_id) { for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { Segment& seg = instance->getSegment(i); + if (seg.palette == palette_id) continue; if (!seg.isActive()) continue; - if (instance->paletteBlend) - seg.startTransition(instance->getTransition()); + seg.startTransition(instance->getTransition()); seg.palette = palette_id; } } @@ -970,9 +970,10 @@ class WS2812FX { // 96 bytes for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { Segment& seg = instance->getSegment(i); if (!seg.isActive()) continue; - if (instance->paletteBlend) - seg.startTransition(instance->getTransition()); + if (seg.mode == pattern_id) continue; + seg.startTransition(instance->getTransition()); instance->setMode(i, pattern_id); + seg.markForReset(); } } static WS2812FX* get_strip() { From c0cecec0a7887bb0d8f30e69f742685f770b9b94 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 10 Aug 2022 13:41:36 -0400 Subject: [PATCH 137/263] Start testing crossfading --- usermods/Tubes/Tubes.h | 31 +++++++++++++++++++++++++++++++ usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/node.h | 5 ----- usermods/Tubes/util.h | 2 ++ usermods/Tubes/virtual_strip.h | 2 +- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index ff12ae99fc..edb91ba0bc 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -38,6 +38,12 @@ class TubesUsermod : public Usermod { void setup() { randomize(); + // Override some behaviors on all Tubes + bootPreset = 0; // Try to prevent initial playlists from starting + fadeTransition = true; // Fade palette transitions + transitionDelay = 8000; // Fade them for a long time + WS2812FX::load_pattern(DEFAULT_WLED_FX); // Crossfade with FLOW + // Start timing globalTimer.setup(); beats.setup(); @@ -61,6 +67,31 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { + // Perform a cross-fade between current WLED mode and the external buffer + + // uint8_t segment_id = strip.getMainSegmentId(); + uint16_t length = strip.getLengthTotal(); + auto external_buffer = strip.get_external_buffer(); + + uint8_t fade = sin8(millis() / 40); // amount that Tubes overwrites WLED, 0-255 + + if (fade > 0) { + for (int i = 0, p = 0; i < length; i++, p++) { + if (p >= EXTERNAL_BUFFER_SIZE) { + p = 0; + } + + CRGB color1 = strip.getPixelColor(i); + CRGB color2 = external_buffer[p]; + + uint8_t r = blend8(color1.r, color2.r, fade); + uint8_t g = blend8(color1.g, color2.g, fade); + uint8_t b = blend8(color1.b, color2.b, fade); + + strip.setPixelColor(i, CRGB(r,g,b)); + } + } + // Draw effects layers over whatever WLED is doing. WS2812FX* leds = &strip; controller.effects->draw(leds); diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 58252d44d4..2ab09732f3 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -11,7 +11,7 @@ class LEDs { CRGB leds[MAX_REAL_LEDS]; // CRGB led_array[MAX_REAL_LEDS]; - const static int FRAMES_PER_SECOND = 300; // how often we refresh the strip, in frames per second + const static int FRAMES_PER_SECOND = 150; // how often we refresh the strip, in frames per second const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we refresh the strip, in milliseconds int num_leds; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 58f3f40b87..57a91568a9 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -13,7 +13,6 @@ #include #include "global_state.h" -#include "wled.h" // #define NODE_DEBUGGING #define TESTING_NODE_ID 100 @@ -94,10 +93,6 @@ class LightNode { } strcpy(apPass, "WledWled"); apBehavior = AP_BEHAVIOR_NO_CONN; - - bootPreset = 0; // Try to prevent initial playlists from starting - fadeTransition = true; - transitionDelay = 6000; } void onWifiConnect() { diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index 60371c52d5..e49f208697 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -5,6 +5,8 @@ // Is this the tube that can control others? #define MASTER_TUBE +#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS + uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { uint16_t rangewidth = highest - lowest; uint16_t scaledbeat = scale16( v, rangewidth ); diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 5b12411e42..1ef3b0fcc0 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -77,7 +77,7 @@ class VirtualStrip { #ifdef MASTER_TUBE // Interface with WLED WS2812FX::load_palette(background.palette_id); - WS2812FX::load_pattern(FX_MODE_EXTERNAL); + WS2812FX::load_pattern(DEFAULT_WLED_FX); stateChanged = true; stateUpdated(CALL_MODE_DIRECT_CHANGE); #endif From 8c77c76cf89075dee5586367ef6995429d1b5600 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 00:08:16 -0400 Subject: [PATCH 138/263] Introduce a root relay for commands --- usermods/Tubes/Tubes.h | 17 +++- usermods/Tubes/controller.h | 172 ++++++++++++++++++++++-------------- usermods/Tubes/node.h | 137 ++++++++++++++++------------ usermods/Tubes/options.h | 9 +- 4 files changed, 212 insertions(+), 123 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index edb91ba0bc..2215c134c6 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -73,7 +73,22 @@ class TubesUsermod : public Usermod { uint16_t length = strip.getLengthTotal(); auto external_buffer = strip.get_external_buffer(); - uint8_t fade = sin8(millis() / 40); // amount that Tubes overwrites WLED, 0-255 + uint8_t fade; // amount that Tubes overwrites WLED, 0-255 + switch (this->controller.options.fader) { + case AUTO: + default: + fade = sin8(millis() / 40); + break; + case LEFT: + fade = 255; + break; + case RIGHT: + fade = 0; + break; + case MIDDLE: + fade = 127; + break; + } if (fade > 0) { for (int i = 0, p = 0; i < length; i++, p++) { diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 60eabb4018..967554a95f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -21,8 +21,13 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; typedef struct { bool debugging; uint8_t brightness; + Fader fader; // temp } ControllerOptions; +typedef struct { + TubeState current; + TubeState next; +} TubeStates; #define NUM_VSTRIPS 3 @@ -104,7 +109,6 @@ class PatternController : public MessageReceiver { this->vstrips[i] = new VirtualStrip(num_leds); #endif } - } void setup(bool isMaster) @@ -113,6 +117,7 @@ class PatternController : public MessageReceiver { this->isMaster = isMaster; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + this->options.fader = AUTO; #ifdef USELCD this->lcd->setup(); @@ -245,7 +250,7 @@ class PatternController : public MessageReceiver { Serial.print(F(" ")); Serial.println(); - this->node->update_status(this->current_state, this->next_state); + this->broadcast_state(); } void background_changed() { @@ -364,20 +369,12 @@ class PatternController : public MessageReceiver { this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; } - void optionsChanged() { -#ifdef NOT_COMPLETE - if (this->isMaster) { - this->radio->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); - } -#endif - } - void setBrightness(uint8_t brightness) { Serial.print(F("brightness ")); Serial.println(brightness); this->options.brightness = brightness; - this->optionsChanged(); + this->broadcast_options(); } void setDebugging(bool debugging) { @@ -385,7 +382,7 @@ class PatternController : public MessageReceiver { Serial.println(debugging); this->options.debugging = debugging; - this->optionsChanged(); + this->broadcast_options(); } SyncMode randomSyncMode() { @@ -436,50 +433,6 @@ class PatternController : public MessageReceiver { addFlash(CRGB::Green); } - virtual void onCommand(CommandId command, void *data) { - switch (command) { - case COMMAND_RESET: - Serial.println(F("reset")); - return; - - case COMMAND_BRIGHTNESS: { - uint8_t *bright = (uint8_t *)data; - this->setBrightness(*bright); - Serial.println(); - return; - } - - case COMMAND_OPTIONS: { - Serial.println(F("options")); - memcpy(&this->options, data, sizeof(this->options)); - return; - } - - case COMMAND_UPDATE: { - Serial.print(F(" update ")); - - auto update_data = (NodeUpdate*)data; - - TubeState state; - memcpy(&state, &update_data->current, sizeof(TubeState)); - memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); - state.print(); - this->next_state.print(); - Serial.println(); - - // Catch up to this state - this->load_pattern(state); - this->load_palette(state); - this->load_effect(state); - this->beats->sync(state.bpm, state.beat_frame); - return; - } - } - - Serial.print(F("UNKNOWN ")); - Serial.println(command, HEX); - } - void read_keys() { if (!Serial.available()) return; @@ -526,6 +479,7 @@ class PatternController : public MessageReceiver { } void keyboard_command(char *command) { + // If not the lead, send it to the lead. uint8_t b; accum88 arg = this->parse_number(command+1); @@ -578,33 +532,71 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.pattern_id = arg >> 8; this->next_state.pattern_sync_id = All; - this->update_next(); + this->broadcast_state(); return; - + + case '[': + switch (this->options.fader) { + case LEFT: + this->options.fader = AUTO; + break; + + case RIGHT: + this->options.fader = MIDDLE; + break; + + case MIDDLE: + case AUTO: + default: + this->options.fader = LEFT; + break; + } + this->broadcast_options(); + return; + + case ']': + switch (this->options.fader) { + case RIGHT: + this->options.fader = AUTO; + break; + + case LEFT: + this->options.fader = MIDDLE; + break; + + case MIDDLE: + case AUTO: + default: + this->options.fader = RIGHT; + break; + } + this->broadcast_options(); + return; + case 'm': this->next_state.pattern_phrase = 0; this->next_state.pattern_id = this->current_state.pattern_id; this->next_state.pattern_sync_id = arg >> 8; - this->update_next(); + this->broadcast_state(); return; case 'c': this->next_state.palette_phrase = 0; this->next_state.palette_id = arg >> 8; - this->update_next(); + this->broadcast_state(); return; case 'e': this->next_state.effect_phrase = 0; this->next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; - this->update_next(); + this->broadcast_state(); return; case '%': this->next_state.effect_phrase = 0; this->next_state.effect_params = this->current_state.effect_params; this->next_state.effect_params.chance = arg; - this->update_next(); + this->broadcast_state(); return; case 'g': @@ -626,6 +618,10 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); return; + case 0: + // Empty command + return; + default: Serial.println("dunno?"); return; @@ -638,11 +634,57 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase -= next_phrase; this->next_state.palette_phrase -= next_phrase; this->next_state.effect_phrase -= next_phrase; - this->update_next(); + this->broadcast_state(); + } + + void broadcast_state() { + this->node->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(TubeStates)); } - void update_next() { - this->node->update_status(this->current_state, this->next_state); + void broadcast_options() { + this->node->sendCommand(COMMAND_OPTIONS, &this->options, sizeof(this->options)); + } + + virtual void onCommand(CommandId command, void *data) { + switch (command) { + case COMMAND_RESET: + Serial.println(F("reset")); + return; + + case COMMAND_BRIGHTNESS: { + uint8_t *bright = (uint8_t *)data; + this->setBrightness(*bright); + Serial.println(); + return; + } + + case COMMAND_OPTIONS: + Serial.println(F("options")); + memcpy(&this->options, data, sizeof(this->options)); + return; + + case COMMAND_UPDATE: { + Serial.print(F(" update ")); + + auto update_data = (TubeStates*)data; + + TubeState state; + memcpy(&state, &update_data->current, sizeof(TubeState)); + memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); + state.print(); + this->next_state.print(); + Serial.println(); + + // Catch up to this state + this->load_pattern(state); + this->load_palette(state); + this->load_effect(state); + this->beats->sync(state.bpm, state.beat_frame); + return; + } + } + + Serial.printf("UNKNOWN COMMAND %02X", command); } }; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 57a91568a9..8c929cec5e 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -32,17 +32,18 @@ typedef struct { uint8_t version = CURRENT_NODE_VERSION; } MeshNodeHeader; -typedef struct { - TubeState current; - TubeState next; -} NodeUpdate; +typedef enum{ + ALL=0, + ROOT=1, +} MessageRecipients; + +#define MESSAGE_DATA_SIZE 50 typedef struct { MeshNodeHeader header; + MessageRecipients recipients; CommandId command; - union { - NodeUpdate update; - } data; + byte data[MESSAGE_DATA_SIZE] = {0}; } NodeMessage; void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); @@ -82,19 +83,6 @@ class LightNode { this->receiver = receiver; } - void configure_ap() { - // Try to hide the access point unless this is the "root" node - strcpy(clientSSID, ""); - strcpy(clientPass, ""); - if (this->is_following()) { - sprintf(apSSID, "WLED %03X", this->header.id); - } else { - sprintf(apSSID, "WLED %03X", this->header.id); - } - strcpy(apPass, "WledWled"); - apBehavior = AP_BEHAVIOR_NO_CONN; - } - void onWifiConnect() { if (this->status == NODE_STATUS_QUIET) return; @@ -114,7 +102,7 @@ class LightNode { WiFi.mode (WIFI_MODE_STA); WiFi.disconnect(false, true); quickEspNow.begin(1, WIFI_IF_STA); - this->initialize(); + this->start(); } void onMeshChange() { @@ -126,6 +114,27 @@ class LightNode { this->configure_ap(); } + void configure_ap() { + // Try to hide the access point unless this is the "root" node + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + if (this->is_following()) { + sprintf(apSSID, "WLED %03X F", this->header.id); + } else { + sprintf(apSSID, "WLED %03X", this->header.id); + } + strcpy(apPass, "WledWled"); + apBehavior = AP_BEHAVIOR_NO_CONN; + } + + void start() { + // Initialization timer: wait for a bit before trying to broadcast. + // If this node's ID is high, it's more likely to be the leader, so wait less. + this->status = NODE_STATUS_STARTING; + this->statusTimer.start(3000 - this->header.id / 2); + this->rebroadcastTimer.stop(); + } + void onPeerPing(MeshNodeHeader* node) { // When receiving a message, if the IDs match, it's a conflict // Reset to create a new ID. @@ -179,28 +188,49 @@ class LightNode { // Track that another node exists, updating this node's understanding of the mesh. this->onPeerPing(&message->header); - // Ignore this message if not from my uplink - if (message->header.id != this->header.uplinkId) { + bool ignore = false; + switch (message->recipients) { + case ALL: + // Ignore this message if not from the uplink + ignore = (message->header.id != this->header.uplinkId); + break; + + case ROOT: + // Ignore this message if not from a downlink + ignore = (message->header.uplinkId != this->header.id); + break; + + default: + // ignore this! + ignore = true; + break; + } + + if (ignore) { #ifdef NODE_DEBUGGING Serial.printf(" ignoring\n"); #endif return; - } - + } else { #ifdef NODE_DEBUGGING - Serial.printf(" listening\n"); + Serial.printf(" listening\n"); #endif + } // Execute the received command - Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); - this->receiver->onCommand( - message->command, - &message->data - ); + if (message->recipients != ROOT || !this->is_following()) { + Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); + this->receiver->onCommand( + message->command, + &message->data + ); + } // Re-broadcast the message if appropriate if (!this->rebroadcastTimer.ended()) { message->header = this->header; + if (!this->is_following()) + message->recipients = ALL; this->broadcast(message, true); } } @@ -218,9 +248,26 @@ class LightNode { Serial.printf(">> Broadcast error %d\n", err); } - void setup() { - configure_ap(); + void sendCommand(CommandId command, void *data, uint8_t len) { + if (len > MESSAGE_DATA_SIZE) { + Serial.println("Message is too big!"); + return; + } + NodeMessage message; + message.header = header; + if (this->is_following()) { + // Follower nodes must request that the root re-sends this message + message.recipients = ROOT; + } else { + message.recipients = ALL; + } + message.command = command; + memcpy(&message.data, data, len); + this->broadcast(&message); + } + + void setup() { #ifdef NODE_DEBUGGING this->reset(TESTING_NODE_ID); #else @@ -228,15 +275,8 @@ class LightNode { #endif this->statusTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); - Serial.println("Node: ok"); - } - void initialize() { - // Initialization timer: wait for a bit before trying to broadcast. - // If this node's ID is high, it's more likely to be the leader, so wait less. - this->status = NODE_STATUS_STARTING; - this->statusTimer.start(3000 - this->header.id / 2); - this->rebroadcastTimer.stop(); + Serial.println("Mesh: ok"); } void update() { @@ -259,21 +299,6 @@ class LightNode { } } - void update_status(TubeState ¤t, TubeState &next) { - // Broadcast (or rebroadcast) the current state - NodeMessage message = { - .header = this->header, - .command = COMMAND_UPDATE, - .data = { - .update = { - .current = current, - .next = next - } - } - }; - this->broadcast(&message); - } - void reset(MeshId id = 0) { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 33467b9869..3599d15032 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -1,6 +1,6 @@ #pragma once -typedef enum SyncMode { +typedef enum SyncMode: uint8_t { All=0, SinDrift=1, Pulse=2, @@ -21,6 +21,13 @@ typedef enum Energy: uint8_t { HighEnergy=20, } Energy; +typedef enum Fader: uint8_t { + AUTO = 0, + LEFT = 1, + MIDDLE = 2, + RIGHT = 3 +} Fader; + typedef struct ControlParameters { public: Duration duration=MediumDuration; From f7ac5a31ad52c710f8778f349c8c62d334231364 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 01:14:32 -0400 Subject: [PATCH 139/263] Prevent duration divide by zero --- usermods/Tubes/controller.h | 7 +++---- usermods/Tubes/debug.h | 8 ++++---- wled00/FX_fcn.cpp | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 967554a95f..ab8dd6e6c9 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -12,7 +12,7 @@ #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -#define STATUS_UPDATE_PERIOD 2000 +#define STATUS_UPDATE_PERIOD 4000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 @@ -161,15 +161,14 @@ class PatternController : public MessageReceiver { // Update patterns to the beat this->update_beat(); - uint16_t phrase = this->current_state.beat_frame >> 12; // Detect manual overrides & update the current state to match. Segment& segment = WS2812FX::get_strip()->getMainSegment(); if (segment.palette != this->current_state.palette_id) { Serial.printf("Palette override = %d\n",segment.palette); + this->next_state.palette_phrase = 0; this->next_state.palette_id = segment.palette; - this->next_state.palette_phrase = phrase; - this->updateTimer.stop(); + this->broadcast_state(); } // if (segment.mode != FX_MODE_EXTERNAL) { // Serial.printf("Pattern override = %d\n",segment.mode); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 9062358ee6..c82426eb0a 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -79,14 +79,14 @@ class DebugController { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; this->strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->node->header.id, this->strip->num_leds-1); - this->strip->leds[p2] = CRGB::White; + uint8_t p2 = scale8(this->controller->node->header.id>>4, this->strip->num_leds-1); + this->strip->leds[p2] = CRGB::Yellow; - uint8_t p3 = scale8(this->controller->node->header.uplinkId, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->strip->num_leds-1); if (p3 == p2) { this->strip->leds[p3] = CRGB::Green; } else { - this->strip->leds[p3] = CRGB::Yellow; + this->strip->leds[p3] = CRGB::Blue; } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index ea9966cf31..55bc366460 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -203,7 +203,6 @@ void Segment::setUpLeds() { else #endif leds = (CRGB*)malloc(sizeof(CRGB)*length()); - } } CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { From 06a0ed2d1954a4546f8ea8f3bdefac7e62189fbd Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 13:11:00 -0400 Subject: [PATCH 140/263] WLED fixes (and revert changes to .H) --- wled00/FX.h | 1 - wled00/wled.h | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index fad540d9d2..e509db9c9b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -973,7 +973,6 @@ class WS2812FX { // 96 bytes if (seg.mode == pattern_id) continue; seg.startTransition(instance->getTransition()); instance->setMode(i, pattern_id); - seg.markForReset(); } } static WS2812FX* get_strip() { diff --git a/wled00/wled.h b/wled00/wled.h index 12a39c5283..1ca2349092 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -12,11 +12,11 @@ //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG -#define WLED_DISABLE_MQTT -#define WLED_DISABLE_LOXONE -#define WLED_DISABLE_ALEXA -#define WLED_DISABLE_INFRARED -#define WLED_DISABLE_CRONIXIE +//#define WLED_DISABLE_MQTT +//#define WLED_DISABLE_LOXONE +//#define WLED_DISABLE_ALEXA +//#define WLED_DISABLE_INFRARED +//#define WLED_DISABLE_CRONIXIE // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. From 6f739ab7948a3f3027c4a65a161cbb3432998828 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 13:11:43 -0400 Subject: [PATCH 141/263] web-based OTA updates + allow wifi connections --- usermods/Tubes/controller.h | 5 ++ usermods/Tubes/node.h | 12 ++--- usermods/Tubes/updater.h | 104 ++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 usermods/Tubes/updater.h diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ab8dd6e6c9..5c345d177a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -2,6 +2,7 @@ #include "wled.h" #include "FX.h" +#include "updater.h" #include "beats.h" @@ -617,6 +618,10 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); return; + case 'U': + WifiUpdater().web_update(); + return; + case 0: // Empty command return; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 8c929cec5e..e8e9b6c12c 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -116,8 +116,6 @@ class LightNode { void configure_ap() { // Try to hide the access point unless this is the "root" node - strcpy(clientSSID, ""); - strcpy(clientPass, ""); if (this->is_following()) { sprintf(apSSID, "WLED %03X F", this->header.id); } else { @@ -291,11 +289,11 @@ class LightNode { if (this->status == NODE_STATUS_STARTING) this->status = NODE_STATUS_STARTED; - // Check WiFi status and update node status if wifi changed - if (WiFi.isConnected()) - this->onWifiConnect(); - else - this->onWifiDisconnect(); + // // Check WiFi status and update node status if wifi changed + // if (WiFi.isConnected()) + // this->onWifiConnect(); + // else + // this->onWifiDisconnect(); } } diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h new file mode 100644 index 0000000000..d35ea1dc40 --- /dev/null +++ b/usermods/Tubes/updater.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include + +// Utility to extract header value from headers +String getHeaderValue(String header, String headerName) { + return header.substring(strlen(headerName.c_str())); +} + +class WifiUpdater { + public: + String host = "kwater.kelectronics.net"; + String bin = "/api/getfirmware/firmwareLarge.bin"; + int port = 80; + + void web_update() { + WiFiClient client; + long fileSize = 0; + String contentType = ""; + + if (!WiFi.isConnected()) { + Serial.println("Not on WiFi"); + return; + } + + Serial.println("Connecting"); + if (!client.connect(host.c_str(), port)) { + Serial.println("Connect failed"); + client.stop(); + return; + } + // Get the contents of the bin file + client.print(String("GET ") + bin + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "Cache-Control: no-cache\r\n" + + "Connection: close\r\n\r\n"); + + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 5000) { + Serial.println("Client Timeout !"); + client.stop(); + return; + } + } + + Serial.println("Reading headers"); + while (client.available()) { + // read line till /n - if the line is empty, it's the end of the headers. + String line = client.readStringUntil('\n'); + line.trim(); + if (!line.length()) break; + + // Check if the HTTP Response is 200 + if (line.startsWith("HTTP/1.1")) { + if (line.indexOf("200") < 0) { + Serial.println("Got a non 200 status code from server. Exiting OTA Update."); + client.flush(); + return; + } + } + + // Read the file size from Content-Length + if (line.startsWith("Content-Length: ")) { + fileSize = atol((getHeaderValue(line, "Content-Length: ")).c_str()); + Serial.println(line); + } + + // Read the content type from Content-Type + if (line.startsWith("Content-Type: ")) { + String contentType = getHeaderValue(line, "Content-Type: "); + Serial.println(line); + } + } + + if (fileSize == 0 || contentType != "application/octet-stream") { + Serial.println("Must get a valid Content-Type and Content-Length header."); + client.flush(); + return; + } + + Serial.println("Beginning update"); + if (!Update.begin(fileSize)) { + Serial.println("Cannot do the update"); + return; + }; + Update.writeStream(client); + if (!Update.end()) { + Serial.println("Error Occurred. Error #: " + String(Update.getError())); + } + + if (Update.isFinished()) { + Serial.println("Update successfully completed. Rebooting."); + ESP.restart(); + } else { + Serial.println("Update not finished? Something went wrong!"); + } + + client.flush(); + return; + } +}; \ No newline at end of file From c4976c3984e12590533fe1d080b33ffff3dd43f5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 11 Aug 2022 17:01:15 -0400 Subject: [PATCH 142/263] Fix small issues in updater --- usermods/Tubes/updater.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index d35ea1dc40..4a2a3927a2 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -54,9 +54,9 @@ class WifiUpdater { if (!line.length()) break; // Check if the HTTP Response is 200 - if (line.startsWith("HTTP/1.1")) { + if (line.startsWith("HTTP/")) { if (line.indexOf("200") < 0) { - Serial.println("Got a non 200 status code from server. Exiting OTA Update."); + Serial.println("Got a non 200 status code"); client.flush(); return; } @@ -70,7 +70,7 @@ class WifiUpdater { // Read the content type from Content-Type if (line.startsWith("Content-Type: ")) { - String contentType = getHeaderValue(line, "Content-Type: "); + contentType = getHeaderValue(line, "Content-Type: "); Serial.println(line); } } From ce90f6b2e5954b63bfea970823d6ab8db30eb4d3 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 01:38:25 -0400 Subject: [PATCH 143/263] better debug info; more frequent updates; remove "external buffer" because it's no longer necessary; revert files to upstream --- usermods/Tubes/Tubes.h | 7 +------ usermods/Tubes/controller.h | 4 +--- usermods/Tubes/debug.h | 30 ++++++++++++++++++++++++------ usermods/Tubes/led_strip.h | 11 +++++++---- usermods/Tubes/node.h | 10 +++++----- usermods/Tubes/util.h | 2 +- wled00/FX.cpp | 19 +------------------ wled00/FX.h | 5 ----- wled00/wled.cpp | 2 -- wled00/wled.h | 7 +------ 10 files changed, 41 insertions(+), 56 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 2215c134c6..d9d16a48ad 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -71,7 +71,6 @@ class TubesUsermod : public Usermod { // uint8_t segment_id = strip.getMainSegmentId(); uint16_t length = strip.getLengthTotal(); - auto external_buffer = strip.get_external_buffer(); uint8_t fade; // amount that Tubes overwrites WLED, 0-255 switch (this->controller.options.fader) { @@ -92,12 +91,8 @@ class TubesUsermod : public Usermod { if (fade > 0) { for (int i = 0, p = 0; i < length; i++, p++) { - if (p >= EXTERNAL_BUFFER_SIZE) { - p = 0; - } - CRGB color1 = strip.getPixelColor(i); - CRGB color2 = external_buffer[p]; + CRGB color2 = controller.led_strip->getPixelColor(i); uint8_t r = blend8(color1.r, color2.r, fade); uint8_t g = blend8(color1.g, color2.g, fade); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 5c345d177a..3a43023b31 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -13,7 +13,7 @@ #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; -#define STATUS_UPDATE_PERIOD 4000 +#define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 @@ -132,8 +132,6 @@ class PatternController : public MessageReceiver { this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); - - WS2812FX::load_pattern(FX_MODE_EXTERNAL); } void do_pattern_changes() { diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c82426eb0a..eba7ddaa39 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -57,20 +57,38 @@ class DebugController { void update() { EVERY_N_MILLISECONDS( 10000 ) { - Serial.printf("\n=== %s%s WiFi %d[ch%d] IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n\n", + // Dump internal status + auto knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + auto knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + Serial.printf("\n=== %s%s WiFi[ch%d] %s IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n", this->controller->node->node_name, status_code(this->controller->node->status).c_str(), - WiFi.status(), WiFi.channel(), - WiFi.localIP()[0], - WiFi.localIP()[1], - WiFi.localIP()[2], - WiFi.localIP()[3], + knownSsid.c_str(), + knownIp[0], + knownIp[1], + knownIp[2], + knownIp[3], freeMemory(), LITTLEFS.usedBytes(), LITTLEFS.totalBytes(), formatted_time(millis()).c_str() ); + + // Dump WLED status + char mode_name[50]; + char palette_name[50]; + auto seg = WS2812FX::getInstance()->getMainSegment(); + extractModeName(seg.mode, JSON_mode_names, mode_name, 50); + extractModeName(seg.palette, JSON_palette_names, palette_name, 50); + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u\n\n", + mode_name, + seg.mode, + palette_name, + seg.palette, + seg.speed, + seg.intensity + ); } // Show the beat on the master OR if debugging diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 2ab09732f3..e46c067714 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -34,10 +34,7 @@ class LEDs { } void show() { - CRGB *external_buffer = WS2812FX::get_external_buffer(); - for (int i = 0; i < num_leds; i++) { - external_buffer[i] = leds[i]; - } + // There's nothing to do right now, because in Tubes.h we blend the LEDs into } void update(bool reverse=false) { @@ -57,4 +54,10 @@ class LEDs { this->fps = 0; } } + + CRGB getPixelColor(uint8_t pos) { + if (pos > this->num_leds) + return CRGB::Black; + return this->leds[pos]; + } }; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index e8e9b6c12c..89304f083b 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -289,11 +289,11 @@ class LightNode { if (this->status == NODE_STATUS_STARTING) this->status = NODE_STATUS_STARTED; - // // Check WiFi status and update node status if wifi changed - // if (WiFi.isConnected()) - // this->onWifiConnect(); - // else - // this->onWifiDisconnect(); + // Check WiFi status and update node status if wifi changed + if (WiFi.isConnected()) + this->onWifiConnect(); + else + this->onWifiDisconnect(); } } diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index e49f208697..2b353c7e1e 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -2,7 +2,7 @@ #include "wled.h" -// Is this the tube that can control others? +// Is this a tube that can control WLED? #define MASTER_TUBE #define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 9f2e798618..453d1dc3f1 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5284,22 +5284,6 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;!,!,!;!;2d"; -uint16_t mode_external(void) { - // uint8_t segment_id = strip.getMainSegmentId(); - uint16_t length = strip.getLengthTotal(); - auto external_buffer = strip.get_external_buffer(); - - for (int i = 0, p = 0; i < length; i++, p++) { - if (p >= EXTERNAL_BUFFER_SIZE) { - p = 0; - } - strip.setPixelColor(i, external_buffer[p]); - } - - return FRAMETIME; -} -static const char _data_FX_MODE_EXTERNAL[] PROGMEM = "External!@!,;!,!,;!;1d"; - //////////////////////////////// // 2D Polar Lights // //////////////////////////////// @@ -7467,8 +7451,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE); addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); - // addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); - addEffect(FX_MODE_EXTERNAL, &mode_external, _data_FX_MODE_EXTERNAL); + addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); // --- 2D non-audio effects --- diff --git a/wled00/FX.h b/wled00/FX.h index e509db9c9b..1f27012335 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -946,12 +946,7 @@ class WS2812FX { // 96 bytes void estimateCurrentAndLimitBri(void); - CRGB external_buffer[EXTERNAL_BUFFER_SIZE]; // 4 bytes per element - public: - static CRGB* get_external_buffer() { - return instance->external_buffer; - } static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { Segment& segment = instance->getMainSegment(); uint32_t color = segment.color_from_palette(c, false, true, 255, brightness); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index f25516b2bf..47c0ed02c7 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -483,8 +483,6 @@ void WLED::initAP(bool resetAP) } DEBUG_PRINT(F("Opening access point ")); DEBUG_PRINTLN(apSSID); - DEBUG_PRINT(F("Password ")); - DEBUG_PRINTLN(apPass); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); diff --git a/wled00/wled.h b/wled00/wled.h index 1ca2349092..c4262dea3b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -12,11 +12,6 @@ //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG -//#define WLED_DISABLE_MQTT -//#define WLED_DISABLE_LOXONE -//#define WLED_DISABLE_ALEXA -//#define WLED_DISABLE_INFRARED -//#define WLED_DISABLE_CRONIXIE // ESP8266-01 (blue) got too little storage space to work with WLED. 0.10.2 is the last release supporting this unit. @@ -49,7 +44,7 @@ #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line -// #define WLED_DEBUG +//#define WLED_DEBUG // filesystem specific debugging //#define WLED_DEBUG_FS From b52b186de2ba7afc0a3baf46166133fac27fd7f5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 01:57:18 -0400 Subject: [PATCH 144/263] Completely back out modifications to core WLED --- usermods/Tubes/Tubes.h | 2 +- usermods/Tubes/controller.h | 2 +- usermods/Tubes/debug.h | 19 +++++++------ usermods/Tubes/particle.h | 4 +-- usermods/Tubes/virtual_strip.h | 52 +++++++++++++++++++++++++--------- wled00/FX.h | 30 +------------------- 6 files changed, 53 insertions(+), 56 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index d9d16a48ad..a7b47939c2 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -42,7 +42,7 @@ class TubesUsermod : public Usermod { bootPreset = 0; // Try to prevent initial playlists from starting fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time - WS2812FX::load_pattern(DEFAULT_WLED_FX); // Crossfade with FLOW + VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX); // Start timing globalTimer.setup(); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3a43023b31..99db4f7092 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -162,7 +162,7 @@ class PatternController : public MessageReceiver { this->update_beat(); // Detect manual overrides & update the current state to match. - Segment& segment = WS2812FX::get_strip()->getMainSegment(); + Segment& segment = strip.getMainSegment(); if (segment.palette != this->current_state.palette_id) { Serial.printf("Palette override = %d\n",segment.palette); this->next_state.palette_phrase = 0; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index eba7ddaa39..404b7f5389 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -2,6 +2,7 @@ #include "controller.h" #include "node.h" +#include "wled.h" std::string formatted_time(long ms) { long secs = ms / 1000; // set the seconds remaining @@ -24,14 +25,14 @@ std::string formatted_time(long ms) { class DebugController { public: PatternController *controller; - LEDs *strip; + LEDs *led_strip; LightNode *node; uint32_t lastPhraseTime; uint32_t lastFrame; DebugController(PatternController *controller) { this->controller = controller; - this->strip = controller->led_strip; + this->led_strip = controller->led_strip; this->node = controller->node; } @@ -78,7 +79,7 @@ class DebugController { // Dump WLED status char mode_name[50]; char palette_name[50]; - auto seg = WS2812FX::getInstance()->getMainSegment(); + auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u\n\n", @@ -95,16 +96,16 @@ class DebugController { if (this->controller->options.debugging) { uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; - this->strip->leds[p1] = CRGB::White; + this->led_strip->leds[p1] = CRGB::White; - uint8_t p2 = scale8(this->controller->node->header.id>>4, this->strip->num_leds-1); - this->strip->leds[p2] = CRGB::Yellow; + uint8_t p2 = scale8(this->controller->node->header.id>>4, this->led_strip->num_leds-1); + this->led_strip->leds[p2] = CRGB::Yellow; - uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->led_strip->num_leds-1); if (p3 == p2) { - this->strip->leds[p3] = CRGB::Green; + this->led_strip->leds[p3] = CRGB::Green; } else { - this->strip->leds[p3] = CRGB::Blue; + this->led_strip->leds[p3] = CRGB::Blue; } } diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index d1afb6a69d..fb0ba887ee 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -97,7 +97,7 @@ class Particle { } void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { - CRGB c = CRGB(WS2812FX::get_strip()->getPixelColor(pos)); + CRGB c = CRGB(strip.getPixelColor(pos)); CRGB new_color; switch (this->pen) { @@ -152,7 +152,7 @@ class Particle { return; } - WS2812FX::get_strip()->setPixelColor(pos, new_color); + strip.setPixelColor(pos, new_color); } }; diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 1ef3b0fcc0..f3ec25cc86 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -4,6 +4,7 @@ #include "options.h" #include "beats.h" #include "palettes.h" +#include "wled.h" #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 @@ -42,6 +43,8 @@ class VirtualStrip { const static uint16_t DEF_BRIGHT = 255; public: + bool isWled = true; + CRGB leds[MAX_VIRTUAL_LEDS]; uint8_t num_leds; uint8_t brightness; @@ -74,15 +77,35 @@ class VirtualStrip { this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; -#ifdef MASTER_TUBE - // Interface with WLED - WS2812FX::load_palette(background.palette_id); - WS2812FX::load_pattern(DEFAULT_WLED_FX); - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); -#endif + if (this->isWled) { + set_wled_palette(background.palette_id); + set_wled_pattern(DEFAULT_WLED_FX); + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } + } + + static void set_wled_palette(uint8_t palette_id) { + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (seg.palette == palette_id) continue; + if (!seg.isActive()) continue; + seg.startTransition(strip.getTransition()); + seg.palette = palette_id; + } + } + + static void set_wled_pattern(uint8_t pattern_id) { + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (!seg.isActive()) continue; + if (seg.mode == pattern_id) continue; + seg.startTransition(strip.getTransition()); + strip.setMode(i, pattern_id); + } } + void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) { if (this->fade == Dead) @@ -166,13 +189,14 @@ class VirtualStrip { } CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { -#define WLED_COLORS -#ifdef WLED_COLORS - return WS2812FX::get_palette_crgb(c + offset, brightness); -#else - CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; - return ColorFromPalette( palette, c + offset ); -#endif + if (this->isWled) { + Segment& segment = strip.getMainSegment(); + uint32_t color = segment.color_from_palette(c + offset, false, true, 255, brightness); + return CRGB(color); + } else { + CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; + return ColorFromPalette( palette, c + offset ); + } } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { diff --git a/wled00/FX.h b/wled00/FX.h index 1f27012335..871af4e751 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -334,6 +334,7 @@ #endif #ifndef USERMOD_AUDIOREACTIVE + #ifndef WLED_DISABLE_AUDIO #define MODE_COUNT 163 #else @@ -945,35 +946,6 @@ class WS2812FX { // 96 bytes void estimateCurrentAndLimitBri(void); - - public: - static CRGB get_palette_crgb(uint16_t c, uint8_t brightness=255U) { - Segment& segment = instance->getMainSegment(); - uint32_t color = segment.color_from_palette(c, false, true, 255, brightness); - return CRGB(color); - } - static void load_palette(uint8_t palette_id) { - for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { - Segment& seg = instance->getSegment(i); - if (seg.palette == palette_id) continue; - if (!seg.isActive()) continue; - seg.startTransition(instance->getTransition()); - seg.palette = palette_id; - } - } - static void load_pattern(uint8_t pattern_id) { - for (uint8_t i=0; i < instance->getSegmentsNum(); i++) { - Segment& seg = instance->getSegment(i); - if (!seg.isActive()) continue; - if (seg.mode == pattern_id) continue; - seg.startTransition(instance->getTransition()); - instance->setMode(i, pattern_id); - } - } - static WS2812FX* get_strip() { - return instance; - } - }; extern const char JSON_mode_names[]; From c5a8f4d44d7e5dcc50bcd071fb0d334cd6805de1 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 02:39:54 -0400 Subject: [PATCH 145/263] Better node debugging --- usermods/Tubes/node.h | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 89304f083b..78cee2d783 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -15,6 +15,7 @@ #include "global_state.h" // #define NODE_DEBUGGING +// #define RELAY_DEBUGGING #define TESTING_NODE_ID 100 #define CURRENT_NODE_VERSION 1 @@ -48,6 +49,18 @@ typedef struct { void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); +char *command_name(CommandId command) { + switch (command) { + case COMMAND_UPDATE: + return "UPDATE"; + case COMMAND_OPTIONS: + return "OPTIONS"; + case COMMAND_RESET: + return "RESET"; + default: + return "?COMMAND?"; + } +} class MessageReceiver { public: @@ -143,8 +156,11 @@ class LightNode { // If the message arrives from a higher ID, switch into follower mode if (node->id > this->header.uplinkId && node->id > this->header.id) { - if (this->header.id != TESTING_NODE_ID || node->id < 0x800) - this->follow(node); +#ifdef RELAY_DEBUGGING + // When debugging relay, pretend not to see any nodes above 0x800 + if (node->id < 0x800) +#endif + this->follow(node); } // If the message arrived from our uplink, track that we're still linked. @@ -170,7 +186,10 @@ class LightNode { NodeMessage* message = (NodeMessage*)data; #ifdef NODE_DEBUGGING Serial.printf(">> Received %db ", len); - Serial.printf("from %03X/%03X ", message->header.id, message->header.uplinkId); + Serial.print(command_name(message->command)); + if (message->recipients == ROOT) + Serial.printf(":ROOT"); + Serial.printf(" from %03X/%03X ", message->header.id, message->header.uplinkId); Serial.printf("at " MACSTR, MAC2STR(address)); Serial.printf("@ %ddBm: ", rssi); #endif @@ -211,7 +230,7 @@ class LightNode { return; } else { #ifdef NODE_DEBUGGING - Serial.printf(" listening\n"); + Serial.printf("\n"); #endif } @@ -238,6 +257,14 @@ class LightNode { if (this->status != NODE_STATUS_STARTED) return; +#ifdef NODE_DEBUGGING + Serial.printf(">> Sending %db ", sizeof(*message)); + Serial.print(command_name(message->command)); + if (message->recipients == ROOT) + Serial.printf(":ROOT"); + Serial.println(); +#endif + auto err = quickEspNow.send( ESPNOW_BROADCAST_ADDRESS, (uint8_t*)message, sizeof(*message) @@ -254,7 +281,7 @@ class LightNode { NodeMessage message; message.header = header; - if (this->is_following()) { + if (command != COMMAND_UPDATE && this->is_following()) { // Follower nodes must request that the root re-sends this message message.recipients = ROOT; } else { From b4040e4cc0eea63e766e3734cb0350950cc0f8eb Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 03:01:31 -0400 Subject: [PATCH 146/263] OPTIONS command --- usermods/Tubes/controller.h | 18 ++++++------------ usermods/Tubes/global_state.h | 4 +--- usermods/Tubes/virtual_strip.h | 4 ++++ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 99db4f7092..76f86bab3f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -257,6 +257,10 @@ class PatternController : public MessageReceiver { Serial.println(); } + void load_options(ControllerOptions &options) { + strip.setBrightness(options.brightness); + } + void load_pattern(TubeState &tube_state) { if (this->current_state.pattern_id == tube_state.pattern_id && this->current_state.pattern_sync_id == tube_state.pattern_sync_id) @@ -650,24 +654,15 @@ class PatternController : public MessageReceiver { virtual void onCommand(CommandId command, void *data) { switch (command) { case COMMAND_RESET: - Serial.println(F("reset")); - return; - - case COMMAND_BRIGHTNESS: { - uint8_t *bright = (uint8_t *)data; - this->setBrightness(*bright); - Serial.println(); + // TODO return; - } case COMMAND_OPTIONS: - Serial.println(F("options")); memcpy(&this->options, data, sizeof(this->options)); + this->load_options(this->options); return; case COMMAND_UPDATE: { - Serial.print(F(" update ")); - auto update_data = (TubeStates*)data; TubeState state; @@ -675,7 +670,6 @@ class PatternController : public MessageReceiver { memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); state.print(); this->next_state.print(); - Serial.println(); // Catch up to this state this->load_pattern(state); diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index bf4ba4f2e5..a19646ec09 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,6 +57,4 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; -const static CommandId COMMAND_RESET = 0xF0; - -const static CommandId COMMAND_BRIGHTNESS = 0x80; +const static CommandId COMMAND_RESET = 0xF0; \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index f3ec25cc86..91d33347b5 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -105,6 +105,10 @@ class VirtualStrip { } } + static void set_wled_brightness(uint8_t brightness) { + strip.setBrightness(brightness); + } + void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) { From 1554cb1fdfee834823c0a72954c5793cce94aa71 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 13 Aug 2022 12:45:12 -0400 Subject: [PATCH 147/263] Better debugging output --- usermods/Tubes/Tubes.h | 1 - usermods/Tubes/node.h | 53 ++++++++++++++++++---------------- usermods/Tubes/util.h | 7 ----- usermods/Tubes/virtual_strip.h | 1 + 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index a7b47939c2..1c9808d6b9 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -42,7 +42,6 @@ class TubesUsermod : public Usermod { bootPreset = 0; // Try to prevent initial playlists from starting fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time - VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX); // Start timing globalTimer.setup(); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 78cee2d783..685fd4cc79 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -21,8 +21,8 @@ #define CURRENT_NODE_VERSION 1 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS -#define UPLINK_TIMEOUT 17000 // Time at which uplink is presumed lost -#define REBROADCAST_TIME 15000 // Time at which followers are presumed re-uplinked +#define UPLINK_TIMEOUT 20000 // Time at which uplink is presumed lost +#define REBROADCAST_TIME 30000 // Time at which followers are presumed re-uplinked #define WIFI_CHECK_RATE 2000 // Time at which we should check wifi status again typedef uint16_t MeshId; @@ -178,26 +178,30 @@ class LightNode { } } + void print_message(NodeMessage* message, signed int rssi) { + Serial.printf("%03X/%03X %s", + message->header.id, + message->header.uplinkId, + command_name(message->command) + ); + if (message->recipients == ROOT) + Serial.printf(":ROOT"); + if (rssi) + Serial.printf(" %ddB ", rssi); + } + void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { // Ignore this message if it isn't a valid message payload. if (len != sizeof(NodeMessage)) return; NodeMessage* message = (NodeMessage*)data; -#ifdef NODE_DEBUGGING - Serial.printf(">> Received %db ", len); - Serial.print(command_name(message->command)); - if (message->recipients == ROOT) - Serial.printf(":ROOT"); - Serial.printf(" from %03X/%03X ", message->header.id, message->header.uplinkId); - Serial.printf("at " MACSTR, MAC2STR(address)); - Serial.printf("@ %ddBm: ", rssi); -#endif - // Ignore this message if it's the wrong version. if (message->header.version != this->header.version) { #ifdef NODE_DEBUGGING - Serial.printf(" recipients != ROOT || !this->is_following()) { - Serial.printf("From %03X/%03X: ", message->header.id, message->header.uplinkId); + Serial.print(" >> "); + print_message(message, rssi); + Serial.print(" "); this->receiver->onCommand( message->command, &message->data ); + Serial.println(); } // Re-broadcast the message if appropriate @@ -258,10 +263,8 @@ class LightNode { return; #ifdef NODE_DEBUGGING - Serial.printf(">> Sending %db ", sizeof(*message)); - Serial.print(command_name(message->command)); - if (message->recipients == ROOT) - Serial.printf(":ROOT"); + Serial.print(" <<< "); + print_message(message, 0); Serial.println(); #endif @@ -270,7 +273,7 @@ class LightNode { (uint8_t*)message, sizeof(*message) ); if (err) - Serial.printf(">> Broadcast error %d\n", err); + Serial.printf(" *** Broadcast error %d\n", err); } void sendCommand(CommandId command, void *data, uint8_t len) { diff --git a/usermods/Tubes/util.h b/usermods/Tubes/util.h index 2b353c7e1e..f0fc596f98 100644 --- a/usermods/Tubes/util.h +++ b/usermods/Tubes/util.h @@ -1,12 +1,5 @@ #pragma once -#include "wled.h" - -// Is this a tube that can control WLED? -#define MASTER_TUBE - -#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS - uint8_t scaled16to8( uint16_t v, uint16_t lowest=0, uint16_t highest=65535) { uint16_t rangewidth = highest - lowest; uint16_t scaledbeat = scale16( v, rangewidth ); diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 91d33347b5..44bd18553d 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -9,6 +9,7 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 +#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS // #define TESTING_PATTERNS class VirtualStrip; From 9b86d5c466b35da4e8bdbccb8763b5f3f4724ec0 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 14 Aug 2022 03:27:44 -0700 Subject: [PATCH 148/263] Power save mode; Ability to set WLED patterns; better debug overlays; Increase number of LEDs; --- usermods/Tubes/Tubes.h | 14 +++-- usermods/Tubes/controller.h | 95 ++++++++++++++++++++++++++++++---- usermods/Tubes/debug.h | 18 ++++--- usermods/Tubes/effects.h | 2 - usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/node.h | 18 ++++++- usermods/Tubes/options.h | 8 +-- usermods/Tubes/pattern.h | 4 -- usermods/Tubes/virtual_strip.h | 15 +++--- 9 files changed, 131 insertions(+), 45 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 1c9808d6b9..89429aed5d 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -42,6 +42,7 @@ class TubesUsermod : public Usermod { bootPreset = 0; // Try to prevent initial playlists from starting fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time + strip.setTargetFps(60); // Start timing globalTimer.setup(); @@ -67,10 +68,6 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { // Perform a cross-fade between current WLED mode and the external buffer - - // uint8_t segment_id = strip.getMainSegmentId(); - uint16_t length = strip.getLengthTotal(); - uint8_t fade; // amount that Tubes overwrites WLED, 0-255 switch (this->controller.options.fader) { case AUTO: @@ -87,9 +84,10 @@ class TubesUsermod : public Usermod { fade = 127; break; } - + if (fade > 0) { - for (int i = 0, p = 0; i < length; i++, p++) { + uint16_t length = strip.getLengthTotal(); + for (int i = 0; i < length; i++) { CRGB color1 = strip.getPixelColor(i); CRGB color2 = controller.led_strip->getPixelColor(i); @@ -102,8 +100,8 @@ class TubesUsermod : public Usermod { } // Draw effects layers over whatever WLED is doing. - WS2812FX* leds = &strip; - controller.effects->draw(leds); + this->controller.overlay(); + this->debug.overlay(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 76f86bab3f..57ab0f2472 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -21,8 +21,15 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; typedef struct { bool debugging; + bool power_save; uint8_t brightness; Fader fader; // temp + + uint8_t wled_pattern; + uint8_t speed; + uint8_t intensity; + + uint8_t reserved[12]; } ControllerOptions; typedef struct { @@ -66,7 +73,7 @@ class Button { class PatternController : public MessageReceiver { public: - const static int FRAMES_PER_SECOND = 300; // how often we animate, in frames per second + const static int FRAMES_PER_SECOND = 100; // how often we animate, in frames per second const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds uint8_t num_leds; @@ -116,9 +123,18 @@ class PatternController : public MessageReceiver { { this->node->setup(); this->isMaster = isMaster; + this->options.power_save = false; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; +#ifdef TESTING_PATTERNS + this->options.fader = RIGHT; +#else this->options.fader = AUTO; +#endif + this->options.wled_pattern = DEFAULT_WLED_FX; + this->options.speed = strip.getMainSegment().speed; + this->options.intensity = strip.getMainSegment().intensity; + this->load_options(this->options); #ifdef USELCD this->lcd->setup(); @@ -169,9 +185,25 @@ class PatternController : public MessageReceiver { this->next_state.palette_id = segment.palette; this->broadcast_state(); } - // if (segment.mode != FX_MODE_EXTERNAL) { - // Serial.printf("Pattern override = %d\n",segment.mode); - // } + + bool options_changed = false; + if (segment.mode != this->options.wled_pattern) { + Serial.printf("WLED FX mode: %d\n",segment.mode); + this->options.wled_pattern = segment.mode; + options_changed = true; + } + if (segment.speed != this->options.speed) { + Serial.printf("WLED FX speed: %d\n",segment.speed); + this->options.speed = segment.speed; + options_changed = true; + } + if (segment.intensity != this->options.intensity) { + Serial.printf("WLED FX intensity: %d\n",segment.intensity); + this->options.intensity = segment.intensity; + options_changed = true; + } + if (options_changed) + this->broadcast_options(); do_pattern_changes(); @@ -201,6 +233,22 @@ class PatternController : public MessageReceiver { #endif } + void overlay() { + if (this->options.power_save) { + // Screen door effect + uint16_t length = strip.getLengthTotal(); + for (int i = 0; i < length; i++) { + if (i % 2) { + CRGB c = strip.getPixelColor(i); + strip.setPixelColor(i, CRGB(c.r>>3,c.g>>3,c.b>>3)); + } + } + } + + // Draw effects layers over whatever WLED is doing. + this->effects->draw(&strip); + } + void restart_phrase() { this->beats->start_phrase(); this->update_beat(); @@ -259,6 +307,7 @@ class PatternController : public MessageReceiver { void load_options(ControllerOptions &options) { strip.setBrightness(options.brightness); + VirtualStrip::set_wled_pattern(options.wled_pattern, options.speed, options.intensity); } void load_pattern(TubeState &tube_state) { @@ -372,25 +421,27 @@ class PatternController : public MessageReceiver { } void setBrightness(uint8_t brightness) { - Serial.print(F("brightness ")); - Serial.println(brightness); + Serial.printf("brightness: %d\n", brightness); this->options.brightness = brightness; this->broadcast_options(); } void setDebugging(bool debugging) { - Serial.print(F("debugging ")); - Serial.println(debugging); + Serial.printf("debugging: %d\n", debugging); this->options.debugging = debugging; this->broadcast_options(); } + void setPowerSave(bool power_save) { + Serial.printf("power_save: %d\n", power_save); + + this->options.power_save = power_save; + this->broadcast_options(); + } + SyncMode randomSyncMode() { - #ifdef TESTING_PATTERNS - return All; - #endif uint8_t r = random8(128); if (r < 40) return SinDrift; @@ -489,6 +540,9 @@ class PatternController : public MessageReceiver { case 'd': this->setDebugging(!this->options.debugging); break; + case '@': + this->setPowerSave(!this->options.power_save); + break; case '-': b = this->options.brightness; @@ -601,6 +655,15 @@ class PatternController : public MessageReceiver { this->broadcast_state(); return; + case 'w': + Serial.printf("Setting WLED FX to %d:128:128\n", arg >> 8); + this->options.wled_pattern = arg >> 8; + this->options.speed = 128; + this->options.intensity = 128; + this->load_options(this->options); + this->broadcast_options(); + return; + case 'g': for (int i=0; i< 10; i++) addGlitter(); @@ -614,10 +677,12 @@ class PatternController : public MessageReceiver { Serial.println(F("m### - sync mode")); Serial.println(F("c### - colors")); Serial.println(F("e### - effects")); + Serial.println("w### - wled pattern"); Serial.println(); Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); + Serial.println("@ - power save mode"); return; case 'U': @@ -660,6 +725,14 @@ class PatternController : public MessageReceiver { case COMMAND_OPTIONS: memcpy(&this->options, data, sizeof(this->options)); this->load_options(this->options); + Serial.printf("[debug=%d bri=%d fader=%d wled:%d:%d:%d]", + this->options.debugging, + this->options.brightness, + this->options.fader, + this->options.wled_pattern, + this->options.speed, + this->options.intensity + ); return; case COMMAND_UPDATE: { diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 404b7f5389..c5010fb8f4 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -91,21 +91,25 @@ class DebugController { seg.intensity ); } + } + void overlay() + { // Show the beat on the master OR if debugging - if (this->controller->options.debugging) { + uint16_t num_leds = strip.getLengthTotal(); + uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; - this->led_strip->leds[p1] = CRGB::White; + strip.setPixelColor(p1, CRGB::White); - uint8_t p2 = scale8(this->controller->node->header.id>>4, this->led_strip->num_leds-1); - this->led_strip->leds[p2] = CRGB::Yellow; + uint8_t p2 = scale8(this->controller->node->header.id>>4, num_leds-1); + strip.setPixelColor(p2, CRGB::Yellow); - uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, this->led_strip->num_leds-1); + uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, num_leds-1); if (p3 == p2) { - this->led_strip->leds[p3] = CRGB::Green; + strip.setPixelColor(p3, CRGB::Green); } else { - this->led_strip->leds[p3] = CRGB::Blue; + strip.setPixelColor(p3, CRGB::Blue); } } diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 47308197cd..eabb1c5e3f 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -131,7 +131,6 @@ typedef struct { static const EffectDef gEffects[] = { {{None}, {LongDuration}}, -#ifndef TESTING_PATTERNS {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, @@ -151,6 +150,5 @@ static const EffectDef gEffects[] = { {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, -#endif }; const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index e46c067714..a4aca410c5 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -3,7 +3,7 @@ #define USE_WLED #include "wled.h" -#define MAX_REAL_LEDS 100 +#define MAX_REAL_LEDS 150 class LEDs { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 685fd4cc79..bf1465e8e4 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -16,9 +16,9 @@ // #define NODE_DEBUGGING // #define RELAY_DEBUGGING -#define TESTING_NODE_ID 100 +#define TESTING_NODE_ID 0 -#define CURRENT_NODE_VERSION 1 +#define CURRENT_NODE_VERSION 2 #define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS #define UPLINK_TIMEOUT 20000 // Time at which uplink is presumed lost @@ -43,6 +43,7 @@ typedef enum{ typedef struct { MeshNodeHeader header; MessageRecipients recipients; + uint32_t timebase; CommandId command; byte data[MESSAGE_DATA_SIZE] = {0}; } NodeMessage; @@ -128,6 +129,9 @@ class LightNode { } void configure_ap() { + // Don't connect to any networks. + strcpy(clientSSID, ""); + // Try to hide the access point unless this is the "root" node if (this->is_following()) { sprintf(apSSID, "WLED %03X F", this->header.id); @@ -241,6 +245,15 @@ class LightNode { Serial.print(" >> "); print_message(message, rssi); Serial.print(" "); + + // Adjust the timebase to match uplink + // But only if it's drifting, else animations get jittery + uint32_t new_timebase = message->timebase - millis() + 3; // Factor for network delay + int32_t diff = new_timebase - strip.timebase; + if (diff < -10 || diff > 10) + strip.timebase = new_timebase; + + // Execute the command this->receiver->onCommand( message->command, &message->data @@ -261,6 +274,7 @@ class LightNode { // Don't broadcast anything if this node isn't active. if (this->status != NODE_STATUS_STARTED) return; + message->timebase = strip.timebase + millis(); #ifdef NODE_DEBUGGING Serial.print(" <<< "); diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 3599d15032..e6c0f0eac6 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -22,10 +22,10 @@ typedef enum Energy: uint8_t { } Energy; typedef enum Fader: uint8_t { - AUTO = 0, - LEFT = 1, - MIDDLE = 2, - RIGHT = 3 + AUTO = 0, // Does a sin pattern + LEFT = 1, // All left (internal) + MIDDLE = 2, // 50% internal, 50% WLED + RIGHT = 3 // All right (WLED) } Fader; typedef struct ControlParameters { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index d8a17d13f0..ca1e9b2d59 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -166,9 +166,6 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. PatternDef gPatterns[] = { -#ifdef TESTING_PATTERNS - {tick, LongDuration}, -#else {drawNoise, {ShortDuration}}, {drawNoise, {ShortDuration}}, {drawNoise, {MediumDuration}}, @@ -184,7 +181,6 @@ PatternDef gPatterns[] = { {palette_wave, {MediumDuration}}, {bpm_palette, {ShortDuration}}, {bpm_palette, {MediumDuration, HighEnergy}} -#endif }; /* diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 44bd18553d..b2ff52d733 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -9,8 +9,8 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -#define DEFAULT_WLED_FX FX_MODE_EXPLODING_FIREWORKS -// #define TESTING_PATTERNS +#define DEFAULT_WLED_FX FX_MODE_FLOW +#define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -80,9 +80,6 @@ class VirtualStrip { if (this->isWled) { set_wled_palette(background.palette_id); - set_wled_pattern(DEFAULT_WLED_FX); - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); } } @@ -94,16 +91,22 @@ class VirtualStrip { seg.startTransition(strip.getTransition()); seg.palette = palette_id; } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); } - static void set_wled_pattern(uint8_t pattern_id) { + static void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; if (seg.mode == pattern_id) continue; seg.startTransition(strip.getTransition()); + seg.speed = speed; + seg.intensity = intensity; strip.setMode(i, pattern_id); } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); } static void set_wled_brightness(uint8_t brightness) { From 5eb46f69f9bc3c8a1e67f39146a6f3aea882f169 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 15 Aug 2022 03:47:58 -0700 Subject: [PATCH 149/263] Add WLED patterns into the list of patterns that are mixed --- usermods/Tubes/Tubes.h | 32 -------- usermods/Tubes/controller.h | 138 ++++++++++++++------------------- usermods/Tubes/debug.h | 5 +- usermods/Tubes/options.h | 17 ++++ usermods/Tubes/pattern.h | 89 +++++++++++++++++---- usermods/Tubes/virtual_strip.h | 32 ++++---- 6 files changed, 169 insertions(+), 144 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 89429aed5d..ea33e87624 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -67,38 +67,6 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { - // Perform a cross-fade between current WLED mode and the external buffer - uint8_t fade; // amount that Tubes overwrites WLED, 0-255 - switch (this->controller.options.fader) { - case AUTO: - default: - fade = sin8(millis() / 40); - break; - case LEFT: - fade = 255; - break; - case RIGHT: - fade = 0; - break; - case MIDDLE: - fade = 127; - break; - } - - if (fade > 0) { - uint16_t length = strip.getLengthTotal(); - for (int i = 0; i < length; i++) { - CRGB color1 = strip.getPixelColor(i); - CRGB color2 = controller.led_strip->getPixelColor(i); - - uint8_t r = blend8(color1.r, color2.r, fade); - uint8_t g = blend8(color1.g, color2.g, fade); - uint8_t b = blend8(color1.b, color2.b, fade); - - strip.setPixelColor(i, CRGB(r,g,b)); - } - } - // Draw effects layers over whatever WLED is doing. this->controller.overlay(); this->debug.overlay(); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 57ab0f2472..ca4a28489b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -19,15 +19,12 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define DEBUG_PATTERNS + typedef struct { bool debugging; bool power_save; uint8_t brightness; - Fader fader; // temp - - uint8_t wled_pattern; - uint8_t speed; - uint8_t intensity; uint8_t reserved[12]; } ControllerOptions; @@ -80,7 +77,8 @@ class PatternController : public MessageReceiver { VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; bool isMaster = false; - + uint16_t wled_fader = 0; + Timer graphicsTimer; Timer updateTimer; @@ -126,14 +124,6 @@ class PatternController : public MessageReceiver { this->options.power_save = false; this->options.debugging = false; this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; -#ifdef TESTING_PATTERNS - this->options.fader = RIGHT; -#else - this->options.fader = AUTO; -#endif - this->options.wled_pattern = DEFAULT_WLED_FX; - this->options.speed = strip.getMainSegment().speed; - this->options.intensity = strip.getMainSegment().intensity; this->load_options(this->options); #ifdef USELCD @@ -186,12 +176,8 @@ class PatternController : public MessageReceiver { this->broadcast_state(); } + /* TODO: detect WLED manual overrides bool options_changed = false; - if (segment.mode != this->options.wled_pattern) { - Serial.printf("WLED FX mode: %d\n",segment.mode); - this->options.wled_pattern = segment.mode; - options_changed = true; - } if (segment.speed != this->options.speed) { Serial.printf("WLED FX speed: %d\n",segment.speed); this->options.speed = segment.speed; @@ -204,6 +190,7 @@ class PatternController : public MessageReceiver { } if (options_changed) this->broadcast_options(); + */ do_pattern_changes(); @@ -234,6 +221,30 @@ class PatternController : public MessageReceiver { } void overlay() { + static uint8_t last_fader = 0; + + // Crossfade between the custom pattern engine and WLED + uint8_t fader = this->wled_fader >> 8; + if (fader != last_fader) { + Serial.printf("fader %u ", fader); + last_fader = fader; + } + if (fader < 255) { + // Perform a cross-fade between current WLED mode and the external buffer + uint16_t length = strip.getLengthTotal(); + for (int i = 0; i < length; i++) { + CRGB c = this->led_strip->getPixelColor(i); + if (fader > 0) { + CRGB color2 = strip.getPixelColor(i); + uint8_t r = blend8(c.r, color2.r, fader); + uint8_t g = blend8(c.g, color2.g, fader); + uint8_t b = blend8(c.b, color2.b, fader); + c = CRGB(r,g,b); + } + strip.setPixelColor(i, c); + } + } + if (this->options.power_save) { // Screen door effect uint16_t length = strip.getLengthTotal(); @@ -245,8 +256,10 @@ class PatternController : public MessageReceiver { } } +#ifndef DEBUG_PATTERNS // Draw effects layers over whatever WLED is doing. this->effects->draw(&strip); +#endif } void restart_phrase() { @@ -307,7 +320,6 @@ class PatternController : public MessageReceiver { void load_options(ControllerOptions &options) { strip.setBrightness(options.brightness); - VirtualStrip::set_wled_pattern(options.wled_pattern, options.speed, options.intensity); } void load_pattern(TubeState &tube_state) { @@ -326,11 +338,15 @@ class PatternController : public MessageReceiver { // Choose the pattern to display at the next pattern cycle // Return the number of phrases until the next pattern cycle uint16_t set_next_pattern(uint16_t phrase) { - uint8_t pattern_id = random8(gPatternCount); - PatternDef def = gPatterns[pattern_id]; - if (def.control.energy > this->energy) { - pattern_id = 0; - def = gPatterns[0]; + uint8_t pattern_id; + PatternDef def; + + // Try 10 times to find a pattern that fits the current "energy" + for (int i = 0; i < 10; i++) { + pattern_id = random8(gPatternCount); + def = gPatterns[pattern_id]; + if (def.control.energy < this->energy) + break; } this->next_state.pattern_id = pattern_id; @@ -356,14 +372,15 @@ class PatternController : public MessageReceiver { void _load_palette(uint8_t palette_id) { this->current_state.palette_id = palette_id; - Serial.print(F("Change palette")); - this->background_changed(); + Serial.println("Change palette"); + VirtualStrip::set_wled_palette(palette_id); } // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { - this->next_state.palette_id = random8(gGradientPaletteCount); + // Don't select palette 1 + this->next_state.palette_id = random8(2, gGradientPaletteCount); return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); } @@ -409,6 +426,7 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; + background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; @@ -465,6 +483,8 @@ class PatternController : public MessageReceiver { } lastFrame = beat_frame; + this->wled_fader = 0; + VirtualStrip *first_strip = NULL; for (uint8_t i=0; i < NUM_VSTRIPS; i++) { VirtualStrip *vstrip = this->vstrips[i]; @@ -474,6 +494,10 @@ class PatternController : public MessageReceiver { // Remember the first strip if (first_strip == NULL) first_strip = vstrip; + + // Remember the strip that's actually WLED + if (vstrip->isWled()) + this->wled_fader = vstrip->fader; vstrip->update(beat_frame, beat_pulse); vstrip->blend(this->led_strip->leds, this->led_strip->num_leds, this->options.brightness, vstrip == first_strip); @@ -591,44 +615,6 @@ class PatternController : public MessageReceiver { this->broadcast_state(); return; - case '[': - switch (this->options.fader) { - case LEFT: - this->options.fader = AUTO; - break; - - case RIGHT: - this->options.fader = MIDDLE; - break; - - case MIDDLE: - case AUTO: - default: - this->options.fader = LEFT; - break; - } - this->broadcast_options(); - return; - - case ']': - switch (this->options.fader) { - case RIGHT: - this->options.fader = AUTO; - break; - - case LEFT: - this->options.fader = MIDDLE; - break; - - case MIDDLE: - case AUTO: - default: - this->options.fader = RIGHT; - break; - } - this->broadcast_options(); - return; - case 'm': this->next_state.pattern_phrase = 0; this->next_state.pattern_id = this->current_state.pattern_id; @@ -655,15 +641,11 @@ class PatternController : public MessageReceiver { this->broadcast_state(); return; - case 'w': - Serial.printf("Setting WLED FX to %d:128:128\n", arg >> 8); - this->options.wled_pattern = arg >> 8; - this->options.speed = 128; - this->options.intensity = 128; - this->load_options(this->options); - this->broadcast_options(); + case 'i': + Serial.printf("Reset! ID -> %03X\n", arg >> 4); + this->node->reset(arg >> 4); return; - + case 'g': for (int i=0; i< 10; i++) addGlitter(); @@ -725,13 +707,9 @@ class PatternController : public MessageReceiver { case COMMAND_OPTIONS: memcpy(&this->options, data, sizeof(this->options)); this->load_options(this->options); - Serial.printf("[debug=%d bri=%d fader=%d wled:%d:%d:%d]", + Serial.printf("[debug=%d bri=%d]", this->options.debugging, - this->options.brightness, - this->options.fader, - this->options.wled_pattern, - this->options.speed, - this->options.intensity + this->options.brightness ); return; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c5010fb8f4..9735ce357c 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -82,13 +82,14 @@ class DebugController { auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); - Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u\n\n", + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n\n", mode_name, seg.mode, palette_name, seg.palette, seg.speed, - seg.intensity + seg.intensity, + this->controller->wled_fader ); } } diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index e6c0f0eac6..ce4e18d836 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -28,6 +28,23 @@ typedef enum Fader: uint8_t { RIGHT = 3 // All right (WLED) } Fader; +uint8_t fader_to_8(Fader fader) { + switch (fader) { + case AUTO: + default: + return sin8(millis() / 40); + + case LEFT: + return 255; + + case RIGHT: + return 0; + + case MIDDLE: + return 127; + } +} + typedef struct ControlParameters { public: Duration duration=MediumDuration; diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index ca1e9b2d59..6fd2424713 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -1,6 +1,7 @@ #pragma once #include "virtual_strip.h" +#include "FX.h" void rainbow(VirtualStrip *strip) { @@ -158,7 +159,11 @@ void drawNoise(VirtualStrip *strip) } } +void draw_wled_fx(VirtualStrip *strip) { +} + typedef struct { + uint8_t wled_fx_id; BackgroundFn backgroundFn; ControlParameters control; } PatternDef; @@ -166,23 +171,77 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. PatternDef gPatterns[] = { - {drawNoise, {ShortDuration}}, - {drawNoise, {ShortDuration}}, - {drawNoise, {MediumDuration}}, - {drawNoise, {MediumDuration}}, - {drawNoise, {MediumDuration}}, - {drawNoise, {LongDuration}}, - {drawNoise, {LongDuration}}, - {confetti, {ShortDuration}}, - {confetti, {MediumDuration}}, - - {juggle, {ShortDuration}}, - {palette_wave, {ShortDuration}}, - {palette_wave, {MediumDuration}}, - {bpm_palette, {ShortDuration}}, - {bpm_palette, {MediumDuration, HighEnergy}} + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {LongDuration}}, + {0, drawNoise, {LongDuration}}, + {0, confetti, {ShortDuration}}, + {0, confetti, {MediumDuration}}, + + {0, juggle, {ShortDuration}}, + {0, palette_wave, {ShortDuration}}, + {0, palette_wave, {MediumDuration}}, + {0, bpm_palette, {ShortDuration}}, + {0, bpm_palette, {MediumDuration, HighEnergy}}, + {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 + {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 + {FX_MODE_AURORA, draw_wled_fx, {MediumDuration}}, // 38 + {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 + {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 + {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ShortDuration}},// 52 + {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 + {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 + {FX_MODE_PALETTE, draw_wled_fx, {ShortDuration}},// 65 + {FX_MODE_FIRE_2012, draw_wled_fx, {MediumDuration}}, // 66 + {FX_MODE_BPM, draw_wled_fx, {MediumDuration}}, // 68 + {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 + {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 + {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 + {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 + {FX_MODE_LAKE, draw_wled_fx, {LongDuration}}, // 75 + {FX_MODE_METEOR_SMOOTH, draw_wled_fx, {MediumDuration}}, // 77 + {FX_MODE_STARBURST, draw_wled_fx, {ShortDuration, HighEnergy}}, // 89 + {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ShortDuration}},// 90 + {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 + {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 + {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 + {FX_MODE_PACIFICA, draw_wled_fx, {LongDuration}}, // 101 + {FX_MODE_TWINKLEUP, draw_wled_fx, {LongDuration}}, // 106 + {FX_MODE_NOISEPAL, draw_wled_fx, {LongDuration}}, // 107 + {FX_MODE_PHASEDNOISE, draw_wled_fx, {MediumDuration}}, // 109 + {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}} // 110 }; /* */ const uint8_t gPatternCount = ARRAY_SIZE(gPatterns); + +/* + +WLED OK not great: +4 - Wipe random +8 - Colorloop +15 - Running +18 - Dissolve +27 - Android +36 - Sweep Random +41 - Lighthouse +44 - Tetrix +50 - Two Dots +56 - Tri Fade +67 - Color Waves --- maybe too spastic +70 - Noise 1 +73 - Noise 4 +80 - Twinklefox +104 - Sunrise +108 - Sine Wave +115 - Blends +154 - Plasmoid +157 - Noisemeter + +Jittery, why? +15 is fine, 16 is jittery +*/ \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index b2ff52d733..f19b582abb 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -10,7 +10,6 @@ #define MAX_VIRTUAL_LEDS 150 #define DEFAULT_WLED_FX FX_MODE_FLOW -#define TESTING_PATTERNS class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -18,6 +17,7 @@ typedef void (*BackgroundFn)(VirtualStrip *strip); class Background { public: BackgroundFn animate; + uint8_t wled_fx_id; uint8_t palette_id; SyncMode sync=All; }; @@ -44,8 +44,6 @@ class VirtualStrip { const static uint16_t DEF_BRIGHT = 255; public: - bool isWled = true; - CRGB leds[MAX_VIRTUAL_LEDS]; uint8_t num_leds; uint8_t brightness; @@ -78,11 +76,16 @@ class VirtualStrip { this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; - if (this->isWled) { + if (this->isWled()) { + set_wled_pattern(background.wled_fx_id, 128, 128); set_wled_palette(background.palette_id); } } + bool isWled() { + return this->background.wled_fx_id != 0; + } + static void set_wled_palette(uint8_t palette_id) { for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); @@ -197,24 +200,23 @@ class VirtualStrip { } CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { - if (this->isWled) { - Segment& segment = strip.getMainSegment(); - uint32_t color = segment.color_from_palette(c + offset, false, true, 255, brightness); - return CRGB(color); - } else { - CRGBPalette16 palette = gGradientPalettes[this->background.palette_id]; - return ColorFromPalette( palette, c + offset ); - } + Segment& segment = strip.getMainSegment(); + uint32_t color = segment.color_from_palette(c + offset, false, true, 255, brightness); + return CRGB(color); } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { return CHSV(this->hue + offset, saturation, value); } - void blend(CRGB strip[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { + void blend(CRGB output[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { if (this->fade == Dead) return; + // WLED is blended in elsewhere + if (this->isWled()) + return; + brightness = scale8(this->brightness, brightness); for (unsigned i=0; i < num_leds; i++) { @@ -224,9 +226,9 @@ class VirtualStrip { nscale8x3(c.r, c.g, c.b, brightness); nscale8x3(c.r, c.g, c.b, this->fader>>8); if (overwrite) - strip[i] = c; + output[i] = c; else - strip[i] |= c; + output[i] |= c; } } From 3d6de1d8710bf45a62e81cb0d63b950541c07146 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 15 Aug 2022 14:18:23 -0700 Subject: [PATCH 150/263] Tuning WLED patterns --- usermods/Tubes/Tubes.h | 4 ++-- usermods/Tubes/controller.h | 38 +++++++++++++++++++++++++++++----- usermods/Tubes/debug.h | 2 +- usermods/Tubes/options.h | 9 ++++---- usermods/Tubes/pattern.h | 17 ++++++++++++--- usermods/Tubes/virtual_strip.h | 1 - 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index ea33e87624..d38d9020f1 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -68,8 +68,8 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { // Draw effects layers over whatever WLED is doing. - this->controller.overlay(); - this->debug.overlay(); + this->controller.handleOverlayDraw(); + this->debug.handleOverlayDraw(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ca4a28489b..0e74c0e292 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -142,18 +142,30 @@ class PatternController : public MessageReceiver { void do_pattern_changes() { uint16_t phrase = this->current_state.beat_frame >> 12; + bool changed = false; if (phrase >= this->next_state.pattern_phrase) { + Serial.println("Time to change pattern"); this->load_pattern(this->next_state); this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + changed = true; } if (phrase >= this->next_state.palette_phrase) { + Serial.println("Time to change palette"); this->load_palette(this->next_state); this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + changed = true; } if (phrase >= this->next_state.effect_phrase) { + Serial.println("Time to change effect"); this->load_effect(this->next_state); this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + changed = true; + } + + if (changed) { + this->next_state.print(); + Serial.println(); } } @@ -220,7 +232,7 @@ class PatternController : public MessageReceiver { #endif } - void overlay() { + void handleOverlayDraw() { static uint8_t last_fader = 0; // Crossfade between the custom pattern engine and WLED @@ -246,7 +258,7 @@ class PatternController : public MessageReceiver { } if (this->options.power_save) { - // Screen door effect + // Screen door effectn uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { @@ -335,24 +347,39 @@ class PatternController : public MessageReceiver { this->background_changed(); } + bool isShowingWled() { + return this->current_state.pattern_id >= numInternalPatterns; + } + + // For now, can't crossfade between internal and WLED patterns + // If currently running an WLED pattern, only select from internal patterns. + uint8_t get_valid_next_pattern() { + if (isShowingWled()) + return random8(0, numInternalPatterns); + return random8(0, gPatternCount); + } + // Choose the pattern to display at the next pattern cycle // Return the number of phrases until the next pattern cycle uint16_t set_next_pattern(uint16_t phrase) { uint8_t pattern_id; PatternDef def; + Serial.println("Changing next pattern"); // Try 10 times to find a pattern that fits the current "energy" for (int i = 0; i < 10; i++) { - pattern_id = random8(gPatternCount); + pattern_id = get_valid_next_pattern(); def = gPatterns[pattern_id]; if (def.control.energy < this->energy) break; } + Serial.printf("Next pattern will be %d\n", pattern_id); this->next_state.pattern_id = pattern_id; this->next_state.pattern_sync_id = this->randomSyncMode(); switch (def.control.duration) { + case ExtraShortDuration: return random8(2, 6); case ShortDuration: return random8(5,15); case MediumDuration: return random8(15,25); case LongDuration: return random8(35,45); @@ -379,8 +406,8 @@ class PatternController : public MessageReceiver { // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { - // Don't select palette 1 - this->next_state.palette_id = random8(2, gGradientPaletteCount); + // Don't select the built-in palettes + this->next_state.palette_id = random8(6, gGradientPaletteCount); return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); } @@ -415,6 +442,7 @@ class PatternController : public MessageReceiver { this->next_state.effect_params = def.params; switch (def.control.duration) { + case ExtraShortDuration: return 2; case ShortDuration: return 3; case MediumDuration: return 6; case LongDuration: return 10; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 9735ce357c..d50dc42ff3 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -94,7 +94,7 @@ class DebugController { } } - void overlay() + void handleOverlayDraw() { // Show the beat on the master OR if debugging if (this->controller->options.debugging) { diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index ce4e18d836..8ad4e5c28e 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -9,10 +9,11 @@ typedef enum SyncMode: uint8_t { } SyncMode; typedef enum Duration: uint8_t { - ShortDuration=0, - MediumDuration=10, - LongDuration=20, - ExtraLongDuration=30, + ExtraShortDuration=0, + ShortDuration=10, + MediumDuration=20, + LongDuration=30, + ExtraLongDuration=40, } Duration; typedef enum Energy: uint8_t { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 6fd2424713..77d9fb342e 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -170,6 +170,7 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. +static const uint8_t numInternalPatterns = 14; PatternDef gPatterns[] = { {0, drawNoise, {ShortDuration}}, {0, drawNoise, {ShortDuration}}, @@ -188,10 +189,13 @@ PatternDef gPatterns[] = { {0, bpm_palette, {MediumDuration, HighEnergy}}, {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 + // Make it HighEnergy? or find out why it's sometimes flashy {FX_MODE_AURORA, draw_wled_fx, {MediumDuration}}, // 38 + // TODO: Aurora is too dark? {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 - {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ShortDuration}},// 52 + {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration}},// 52 + // TODO: Kind of boring {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 {FX_MODE_PALETTE, draw_wled_fx, {ShortDuration}},// 65 @@ -199,19 +203,26 @@ PatternDef gPatterns[] = { {FX_MODE_BPM, draw_wled_fx, {MediumDuration}}, // 68 {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 + {FX_MODE_NOISE16_3, draw_wled_fx, {ShortDuration}}, // 72 {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 + // TODO: Noise3 needs to be slowed down, it's a bit spastic {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 + {FX_MODE_LAKE, draw_wled_fx, {ShortDuration}}, // 75 {FX_MODE_LAKE, draw_wled_fx, {LongDuration}}, // 75 {FX_MODE_METEOR_SMOOTH, draw_wled_fx, {MediumDuration}}, // 77 - {FX_MODE_STARBURST, draw_wled_fx, {ShortDuration, HighEnergy}}, // 89 - {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ShortDuration}},// 90 + {FX_MODE_STARBURST, draw_wled_fx, {ExtraShortDuration, HighEnergy}}, // 89 + {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ExtraShortDuration}},// 90 + // TODO: Must be set to only fire from one side {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 + {FX_MODE_PACIFICA, draw_wled_fx, {ShortDuration}}, // 101 {FX_MODE_PACIFICA, draw_wled_fx, {LongDuration}}, // 101 {FX_MODE_TWINKLEUP, draw_wled_fx, {LongDuration}}, // 106 {FX_MODE_NOISEPAL, draw_wled_fx, {LongDuration}}, // 107 {FX_MODE_PHASEDNOISE, draw_wled_fx, {MediumDuration}}, // 109 + {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {LongDuration}}, // 110 {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}} // 110 }; diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index f19b582abb..a4439a73d0 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -103,7 +103,6 @@ class VirtualStrip { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; if (seg.mode == pattern_id) continue; - seg.startTransition(strip.getTransition()); seg.speed = speed; seg.intensity = intensity; strip.setMode(i, pattern_id); From bb12182ad67363771fc1557ae2ce8c7e19485ff7 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 15 Aug 2022 17:15:01 -0700 Subject: [PATCH 151/263] Try to make some patterns a little more interesting --- usermods/Tubes/Tubes.h | 36 -------------------------------- usermods/Tubes/controller.h | 25 +++++++++++++++++----- usermods/Tubes/options.h | 30 ++++----------------------- usermods/Tubes/pattern.h | 38 +++++++++++++++++++++++++++++----- usermods/Tubes/virtual_strip.h | 5 ++++- 5 files changed, 61 insertions(+), 73 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index d38d9020f1..c7bb874763 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -72,39 +72,3 @@ class TubesUsermod : public Usermod { this->debug.handleOverlayDraw(); } }; - - - - -/* -LIST OF GOOD PATTERNS - -Aurora -Dynamic Smooth -Blends -Colortwinkles -Fireworks -Fireworks Starburst -Flow -Lake -Noise 2 -Noise 4 -Pacifica -Plasma -Ripple -Running Dual -Twinklecat -Twinkleup - -MAYBE GOOD PATTERNS -Fillnoise -Gradient -Juggle -Meteor Smooth -Palette -Phased -Saw -Sinelon Dual -Tetrix - -*/ \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 0e74c0e292..73a1a2dd47 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -19,8 +19,6 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define MAX_COLOR_CHANGE_PHRASES 4 // 40 -#define DEBUG_PATTERNS - typedef struct { bool debugging; bool power_save; @@ -97,6 +95,9 @@ class PatternController : public MessageReceiver { TubeState current_state; TubeState next_state; + // When a pattern is boring, spice it up a bit with more effects + bool isBoring = false; + PatternController(uint8_t num_leds, BeatController *beats) { this->num_leds = num_leds; #ifdef USELCD @@ -164,6 +165,12 @@ class PatternController : public MessageReceiver { } if (changed) { + // For now, WLED doesn't handle transitioning pattern & palette well. + // Stagger them + if (this->next_state.pattern_phrase == this->next_state.palette_phrase) { + this->next_state.palette_phrase += random8(1,3); + } + this->next_state.print(); Serial.println(); } @@ -268,10 +275,8 @@ class PatternController : public MessageReceiver { } } -#ifndef DEBUG_PATTERNS // Draw effects layers over whatever WLED is doing. this->effects->draw(&strip); -#endif } void restart_phrase() { @@ -342,6 +347,7 @@ class PatternController : public MessageReceiver { this->current_state.pattern_phrase = tube_state.pattern_phrase; this->current_state.pattern_id = tube_state.pattern_id % gPatternCount; this->current_state.pattern_sync_id = tube_state.pattern_sync_id; + this->isBoring = gPatterns[this->current_state.pattern_id].control.energy == Boring; Serial.print(F("Change pattern ")); this->background_changed(); @@ -435,8 +441,12 @@ class PatternController : public MessageReceiver { uint16_t set_next_effect(uint16_t phrase) { uint8_t effect_num = random8(gEffectCount); + // Pick a random effect to add; boring patterns get better chance at having an effect. EffectDef def = gEffects[effect_num]; - if (def.control.energy > this->energy) + Energy maxEnergy = this->energy; + if (this->isBoring) + maxEnergy = (Energy)((uint8_t)maxEnergy + 10); + if (def.control.energy > maxEnergy) def = gEffects[0]; this->next_state.effect_params = def.params; @@ -489,6 +499,11 @@ class PatternController : public MessageReceiver { SyncMode randomSyncMode() { uint8_t r = random8(128); + + // For boring patterns, up the chance of a sync mode + if (this->isBoring) + r += 20; + if (r < 40) return SinDrift; if (r < 65) diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 8ad4e5c28e..e499def9f6 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -17,34 +17,12 @@ typedef enum Duration: uint8_t { } Duration; typedef enum Energy: uint8_t { - LowEnergy=0, - MediumEnergy=10, - HighEnergy=20, + Boring=0, + LowEnergy=10, + MediumEnergy=20, + HighEnergy=230 } Energy; -typedef enum Fader: uint8_t { - AUTO = 0, // Does a sin pattern - LEFT = 1, // All left (internal) - MIDDLE = 2, // 50% internal, 50% WLED - RIGHT = 3 // All right (WLED) -} Fader; - -uint8_t fader_to_8(Fader fader) { - switch (fader) { - case AUTO: - default: - return sin8(millis() / 40); - - case LEFT: - return 255; - - case RIGHT: - return 0; - - case MIDDLE: - return 127; - } -} typedef struct ControlParameters { public: diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 77d9fb342e..bfac7e7bf5 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -183,8 +183,8 @@ PatternDef gPatterns[] = { {0, confetti, {MediumDuration}}, {0, juggle, {ShortDuration}}, - {0, palette_wave, {ShortDuration}}, - {0, palette_wave, {MediumDuration}}, + {0, palette_wave, {ShortDuration, Boring}}, + {0, palette_wave, {MediumDuration, Boring}}, {0, bpm_palette, {ShortDuration}}, {0, bpm_palette, {MediumDuration, HighEnergy}}, {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 @@ -194,7 +194,7 @@ PatternDef gPatterns[] = { // TODO: Aurora is too dark? {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 - {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration}},// 52 + {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration, Boring}},// 52 // TODO: Kind of boring {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 @@ -253,6 +253,34 @@ WLED OK not great: 154 - Plasmoid 157 - Noisemeter -Jittery, why? -15 is fine, 16 is jittery +LIST OF GOOD PATTERNS + +Aurora +Dynamic Smooth +Blends +Colortwinkles +Fireworks +Fireworks Starburst +Flow +Lake +Noise 2 +Noise 4 +Pacifica +Plasma +Ripple +Running Dual +Twinklecat +Twinkleup + +MAYBE GOOD PATTERNS +Fillnoise +Gradient +Juggle +Meteor Smooth +Palette +Phased +Saw +Sinelon Dual +Tetrix + */ \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index a4439a73d0..85c2486640 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -77,7 +77,10 @@ class VirtualStrip { this->brightness = DEF_BRIGHT; if (this->isWled()) { - set_wled_pattern(background.wled_fx_id, 128, 128); + uint8_t wled_fx_id = background.wled_fx_id; + if (wled_fx_id < 10) + wled_fx_id = DEFAULT_WLED_FX; + set_wled_pattern(wled_fx_id, 128, 128); set_wled_palette(background.palette_id); } } From 5997655e8a2bff9dc6587291430b7e9e3562ddc1 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 16 Aug 2022 02:38:23 -0700 Subject: [PATCH 152/263] adjust pattern balance --- usermods/Tubes/controller.h | 10 ++++----- usermods/Tubes/pattern.h | 42 ++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 73a1a2dd47..f25657f0fd 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -502,15 +502,15 @@ class PatternController : public MessageReceiver { // For boring patterns, up the chance of a sync mode if (this->isBoring) - r += 20; + r -= 20; - if (r < 40) + if (r < 30) return SinDrift; - if (r < 65) + if (r < 50) return Pulse; - if (r < 72) + if (r < 70) return Swing; - if (r < 84) + if (r < 80) return SwingDrift; return All; } diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index bfac7e7bf5..e0e6525faf 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -170,7 +170,7 @@ typedef struct { // List of patterns to cycle through. Each is defined as a separate function below. -static const uint8_t numInternalPatterns = 14; +static const uint8_t numInternalPatterns = 24; PatternDef gPatterns[] = { {0, drawNoise, {ShortDuration}}, {0, drawNoise, {ShortDuration}}, @@ -181,33 +181,47 @@ PatternDef gPatterns[] = { {0, drawNoise, {LongDuration}}, {0, confetti, {ShortDuration}}, {0, confetti, {MediumDuration}}, + {0, juggle, {ShortDuration}}, + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {ShortDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {MediumDuration}}, + {0, drawNoise, {LongDuration}}, + {0, drawNoise, {LongDuration}}, + {0, confetti, {ShortDuration}}, + {0, confetti, {MediumDuration}}, {0, juggle, {ShortDuration}}, + {0, palette_wave, {ShortDuration, Boring}}, {0, palette_wave, {MediumDuration, Boring}}, {0, bpm_palette, {ShortDuration}}, {0, bpm_palette, {MediumDuration, HighEnergy}}, - {FX_MODE_FADE, draw_wled_fx,{ShortDuration}}, // 12 + {FX_MODE_FADE, draw_wled_fx, {ShortDuration, Boring}}, // 12 {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 // Make it HighEnergy? or find out why it's sometimes flashy - {FX_MODE_AURORA, draw_wled_fx, {MediumDuration}}, // 38 + {FX_MODE_AURORA, draw_wled_fx, {MediumDuration, Boring}}, // 38 // TODO: Aurora is too dark? {FX_MODE_GRADIENT, draw_wled_fx,{ShortDuration}}, // 46 {FX_MODE_FAIRYTWINKLE, draw_wled_fx, {LongDuration}}, // 51 {FX_MODE_RUNNING_DUAL, draw_wled_fx, {ExtraShortDuration, Boring}},// 52 - // TODO: Kind of boring + {FX_MODE_DUAL_LARSON_SCANNER, draw_wled_fx, {MediumDuration}}, // 60 {FX_MODE_JUGGLE, draw_wled_fx, {MediumDuration}}, // 64 {FX_MODE_PALETTE, draw_wled_fx, {ShortDuration}},// 65 {FX_MODE_FIRE_2012, draw_wled_fx, {MediumDuration}}, // 66 {FX_MODE_BPM, draw_wled_fx, {MediumDuration}}, // 68 + // TODO: needs to be slowed down {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 {FX_MODE_NOISE16_3, draw_wled_fx, {ShortDuration}}, // 72 {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 // TODO: Noise3 needs to be slowed down, it's a bit spastic {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 + {FX_MODE_LAKE, draw_wled_fx, {ShortDuration}}, // 75 + {FX_MODE_LAKE, draw_wled_fx, {MediumDuration}}, // 75 {FX_MODE_LAKE, draw_wled_fx, {LongDuration}}, // 75 {FX_MODE_METEOR_SMOOTH, draw_wled_fx, {MediumDuration}}, // 77 {FX_MODE_STARBURST, draw_wled_fx, {ExtraShortDuration, HighEnergy}}, // 89 @@ -215,15 +229,26 @@ PatternDef gPatterns[] = { // TODO: Must be set to only fire from one side {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 + {FX_MODE_PLASMA, draw_wled_fx, {ShortDuration}}, // 97 {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 + {FX_MODE_PACIFICA, draw_wled_fx, {ShortDuration}}, // 101 {FX_MODE_PACIFICA, draw_wled_fx, {LongDuration}}, // 101 {FX_MODE_TWINKLEUP, draw_wled_fx, {LongDuration}}, // 106 {FX_MODE_NOISEPAL, draw_wled_fx, {LongDuration}}, // 107 {FX_MODE_PHASEDNOISE, draw_wled_fx, {MediumDuration}}, // 109 {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {ShortDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {MediumDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {MediumDuration}}, // 110 + {FX_MODE_FLOW, draw_wled_fx, {LongDuration}}, // 110 - {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}} // 110 + {FX_MODE_FLOW, draw_wled_fx, {ExtraLongDuration}}, // 110 + {FX_MODE_FIRE_2012, draw_wled_fx, {ShortDuration}}, // 66 + {FX_MODE_FIRE_2012, draw_wled_fx, {MediumDuration}}, // 66 + {FX_MODE_PHASEDNOISE, draw_wled_fx, {ShortDuration}}, // 109 + }; /* @@ -283,4 +308,11 @@ Saw Sinelon Dual Tetrix + +Colors to fix: +72 - replace with something else +35 - drops framerate +62 - drops framerate +61 - drops framerate + */ \ No newline at end of file From 70a8ceac388242bbda093cd2eb62637211e5b2e3 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 17 Aug 2022 17:48:54 -0700 Subject: [PATCH 153/263] replace monochrome palettes --- wled00/FX_fcn.cpp | 2 +- wled00/palettes.h | 58 +++++++++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 55bc366460..6ec39019f6 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1597,7 +1597,7 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", "Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", "Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18", -"Calpan","Calbayo","Fib53","Grindylow 15","Grindylow 21","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", +"Calpan","Calbayo","Fib53","Purple/Orange","PMH","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", "Autumn 04","Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10", "Landscape 76","Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10", "Vintage 56","Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" diff --git a/wled00/palettes.h b/wled00/palettes.h index c2ab9295bf..625675b63f 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -951,27 +951,47 @@ DEFINE_PALETTE( fib53_15_gp ) { 253, 1, 1, 1, 255, 239,241,240}; -// Gradient palette "grindylow_15_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-15.png.index.html +// Gradient palette "purple_orange_d08_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/km/tn/purple-orange-d08.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 12 bytes of program space. - -DEFINE_PALETTE( grindylow_15_gp ) { - 0, 101,241,105, - 127, 26,182,105, - 255, 26,151, 80}; +// Size: 64 bytes of program space. -// Gradient palette "grindylow_21_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/grindylow/tn/grindylow-21.png.index.html +DEFINE_GRADIENT_PALETTE( purple_orange_d08_gp ) { + 0, 49, 26, 89, + 31, 49, 26, 89, + 31, 107, 49,106, + 63, 107, 49,106, + 63, 165, 88,127, + 95, 165, 88,127, + 95, 188,151,158, + 127, 188,151,158, + 127, 210,178,117, + 159, 210,178,117, + 159, 239,135, 37, + 191, 239,135, 37, + 191, 220, 81, 7, + 223, 220, 81, 7, + 223, 159, 37, 1, + 255, 159, 37, 1}; + +// Gradient palette "pmh_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/esdb/tn/pmh.png.index.html // converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. +// Size: 48 bytes of program space. -DEFINE_PALETTE( grindylow_21_gp ) { - 0, 101,241,105, - 51, 60,241,240, - 128, 26,241,240, - 191, 26,182,105, - 255, 26,151, 80}; +DEFINE_PALETTE( pmh_gp ) { + 0, 255, 55,145, + 42, 255, 55,145, + 42, 255,182,145, + 84, 255,182,145, + 84, 255,255,105, + 127, 255,255,105, + 127, 171,255,174, + 170, 171,255,174, + 170, 101,255,212, + 212, 101,255,212, + 212, 171, 82,212, + 255, 171, 82,212}; // Gradient palette "konjo_08_gp", originally from // http://soliton.vm.bytemark.co.uk/pub/cpt-city/gacruxa/konjo/tn/konjo-08.png.index.html @@ -2045,8 +2065,8 @@ const byte* const gGradientPalettes[] PROGMEM = { calpan_18_gp, calbayo_18_gp, fib53_15_gp, - grindylow_15_gp, - grindylow_21_gp, + purple_orange_d08_gp, + pmh_gp, konjo_08_gp, konkikyo_19_gp, mccahon_16_gp, From 41cb4c0b591484661a43279ee407e564df9813cd Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 17 Aug 2022 21:05:03 -0700 Subject: [PATCH 154/263] rebalance the effects so the defaults are generally chill --- usermods/Tubes/controller.h | 16 ++++++++++++++-- usermods/Tubes/effects.h | 32 +++++++++++++++++--------------- usermods/Tubes/node.h | 2 +- usermods/Tubes/options.h | 8 ++++---- wled00/palettes.h | 2 +- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index f25657f0fd..9db41590f5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -19,6 +19,8 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; #define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define IDENTIFY_STUCK_PATTERNS + typedef struct { bool debugging; bool power_save; @@ -91,7 +93,7 @@ class PatternController : public MessageReceiver { ControllerOptions options; char key_buffer[20] = {0}; - Energy energy=LowEnergy; + Energy energy=Chill; TubeState current_state; TubeState next_state; @@ -146,19 +148,25 @@ class PatternController : public MessageReceiver { bool changed = false; if (phrase >= this->next_state.pattern_phrase) { +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change pattern"); +#endif this->load_pattern(this->next_state); this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); changed = true; } if (phrase >= this->next_state.palette_phrase) { +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change palette"); +#endif this->load_palette(this->next_state); this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); changed = true; } if (phrase >= this->next_state.effect_phrase) { +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change effect"); +#endif this->load_effect(this->next_state); this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); changed = true; @@ -306,7 +314,7 @@ class PatternController : public MessageReceiver { else if (this->current_state.bpm > 120>>8) this->energy = MediumEnergy; else - this->energy = LowEnergy; + this->energy = Chill; } void send_update() { @@ -371,7 +379,9 @@ class PatternController : public MessageReceiver { uint8_t pattern_id; PatternDef def; +#ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Changing next pattern"); +#endif // Try 10 times to find a pattern that fits the current "energy" for (int i = 0; i < 10; i++) { pattern_id = get_valid_next_pattern(); @@ -379,7 +389,9 @@ class PatternController : public MessageReceiver { if (def.control.energy < this->energy) break; } +#ifdef IDENTIFY_STUCK_PATTERNS Serial.printf("Next pattern will be %d\n", pattern_id); +#endif this->next_state.pattern_id = pattern_id; this->next_state.pattern_sync_id = this->randomSyncMode(); diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index eabb1c5e3f..4907bdfd4f 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -131,24 +131,26 @@ typedef struct { static const EffectDef gEffects[] = { {{None}, {LongDuration}}, - {{Flash, Brighten, Beat, 40}, {MediumDuration, MediumEnergy}}, - {{Flash, Darken, TwoBeats, 40}, {MediumDuration, MediumEnergy}}, - {{Flash, Brighten, Measure}, {ShortDuration, HighEnergy}}, - {{Flash, Brighten, Phrase}, {MediumDuration, HighEnergy}}, - {{Flash, Darken, Measure}, {ShortDuration, LowEnergy}}, - {{Glitter, Brighten, Eighth, 40}, {ShortDuration, LowEnergy}}, + {{Flash, Brighten, Beat, 40}, {MediumDuration, HighEnergy}}, + {{Flash, Darken, TwoBeats, 40}, {MediumDuration, HighEnergy}}, + {{Flash, Brighten, Measure}, {ShortDuration, MediumEnergy}}, + {{Flash, Brighten, Phrase}, {MediumDuration, MediumEnergy}}, + {{Flash, Darken, Measure}, {ShortDuration, MediumEnergy}}, + {{Glitter, Brighten, Eighth, 40}, {ShortDuration, Chill}}, {{Glitter, Brighten, Eighth, 80}, {MediumDuration, MediumEnergy}}, {{Glitter, Brighten, Eighth, 40}, {MediumDuration, HighEnergy}}, - {{Glitter, Darken, Eighth, 40}, {MediumDuration, LowEnergy}}, + {{Glitter, Darken, Eighth, 40}, {MediumDuration, Chill}}, - {{Glitter, Draw, Eighth, 10}, {LongDuration, LowEnergy}}, - {{Glitter, Draw, Eighth, 120}, {MediumDuration, LowEnergy}}, - {{Glitter, Invert, Eighth, 40}, {ShortDuration, LowEnergy}}, - {{Beatbox2, Black}, {MediumDuration, LowEnergy}}, + {{Glitter, Draw, Eighth, 10}, {LongDuration, Chill}}, + {{Glitter, Draw, Eighth, 120}, {MediumDuration, Chill}}, + {{Glitter, Invert, Eighth, 40}, {ShortDuration, Chill}}, + {{Beatbox2, Black}, {MediumDuration, MediumEnergy}}, {{Beatbox2, Draw}, {ShortDuration, HighEnergy}}, - {{Bubble, Darken}, {MediumDuration, LowEnergy}}, - {{Bubble, Brighten}, {MediumDuration, LowEnergy}}, - {{Glitter, Darken, Eighth, 120}, {MediumDuration, LowEnergy}}, - {{Glitter, Flicker, Eighth, 120}, {MediumDuration, LowEnergy}}, + {{Bubble, Darken}, {MediumDuration, Chill}}, + {{Bubble, Brighten}, {MediumDuration, Chill}}, + {{Bubble, Brighten}, {MediumDuration, Chill}}, + {{Bubble, Invert}, {MediumDuration, Chill}}, + {{Glitter, Darken, Eighth, 120}, {MediumDuration, MediumEnergy}}, + {{Glitter, Flicker, Eighth, 120}, {MediumDuration, Chill}}, }; const uint8_t gEffectCount = ARRAY_SIZE(gEffects); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index bf1465e8e4..15e2235310 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -177,7 +177,7 @@ class LightNode { // enter or continue re-broadcasting mode. if (node->uplinkId == this->header.id || (node->uplinkId == 0 && node->id < this->header.id)) { - Serial.printf("%03X/%03X is following me\n", node->id, node->uplinkId); + Serial.printf(" %03X/%03X is following me\n", node->id, node->uplinkId); this->rebroadcastTimer.start(REBROADCAST_TIME); } } diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index e499def9f6..769c172a19 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -17,8 +17,8 @@ typedef enum Duration: uint8_t { } Duration; typedef enum Energy: uint8_t { - Boring=0, - LowEnergy=10, + Boring=0, // a "boring" pattern is slow or whatever but -needs- effects to be interesting + Chill=10, // A "chill" pattern is only slow fades, no flashes MediumEnergy=20, HighEnergy=230 } Energy; @@ -27,9 +27,9 @@ typedef enum Energy: uint8_t { typedef struct ControlParameters { public: Duration duration=MediumDuration; - Energy energy=LowEnergy; + Energy energy=Chill; - ControlParameters(Duration duration=MediumDuration, Energy energy=LowEnergy) { + ControlParameters(Duration duration=MediumDuration, Energy energy=Chill) { this->duration=duration; this->energy=energy; }; diff --git a/wled00/palettes.h b/wled00/palettes.h index 625675b63f..e664bfd9ce 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -956,7 +956,7 @@ DEFINE_PALETTE( fib53_15_gp ) { // converted for FastLED with gammas (2.6, 2.2, 2.5) // Size: 64 bytes of program space. -DEFINE_GRADIENT_PALETTE( purple_orange_d08_gp ) { +DEFINE_PALETTE( purple_orange_d08_gp ) { 0, 49, 26, 89, 31, 49, 26, 89, 31, 107, 49,106, From 47712372b5b2aebf0ffbd3230fc2260e6106abc0 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 18 Aug 2022 00:36:42 -0700 Subject: [PATCH 155/263] New colors --- usermods/Tubes/controller.h | 7 +- usermods/Tubes/pattern.h | 2 +- wled00/FX_fcn.cpp | 13 +- wled00/const.h | 2 +- wled00/palettes.h | 266 ++++++++++++++++++++++++++++++++++-- 5 files changed, 266 insertions(+), 24 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 9db41590f5..66c937436e 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -138,6 +138,7 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; + VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX, 128, 128); this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); @@ -248,14 +249,8 @@ class PatternController : public MessageReceiver { } void handleOverlayDraw() { - static uint8_t last_fader = 0; - // Crossfade between the custom pattern engine and WLED uint8_t fader = this->wled_fader >> 8; - if (fader != last_fader) { - Serial.printf("fader %u ", fader); - last_fader = fader; - } if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer uint16_t length = strip.getLengthTotal(); diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index e0e6525faf..14946808bc 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -228,7 +228,7 @@ PatternDef gPatterns[] = { {FX_MODE_EXPLODING_FIREWORKS, draw_wled_fx, {ExtraShortDuration}},// 90 // TODO: Must be set to only fire from one side {FX_MODE_SINELON_DUAL, draw_wled_fx, {MediumDuration}}, // 93 - {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration}}, // 95 + {FX_MODE_POPCORN, draw_wled_fx, {ShortDuration, MediumEnergy}}, // 95 {FX_MODE_PLASMA, draw_wled_fx, {ShortDuration}}, // 97 {FX_MODE_PLASMA, draw_wled_fx, {LongDuration}}, // 97 diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 6ec39019f6..794676f9e8 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1595,11 +1595,12 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "Hult 64","Drywet","IB15","Fuschia","Emerald Dragon 08","Hot Lava","Fire","Hiyane","Colorfull","Magenta Evening", "Pink Purple","Sunset","Autumn","Blue/Magenta/White","Blue/Magenta/Red","Blue/Red/Yellow","Blue/Cyan/Yellow","Sunset Yellow","Cloud","Fire & Ice", "BHW2","Rainfall","Angel","Butterfly","250K Meters","Night Midnight","Afterdusk","Blue Sky","Gold Orange", "Frizell 10", -"Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G", -"Wild Orange","Ikat","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18", -"Calpan","Calbayo","Fib53","Purple/Orange","PMH","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange", -"Autumn 04","Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10", -"Landscape 76","Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10", -"Vintage 56","Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Janico" +"Frizell 12","Fib 18","Fib 13","Fib 17","Fib 05","Analogous 02","Analogous 04a","Cyan Orange","C/W/G","Wild Orange", +"Ikat Radial","Citrus","Teal Blue","Ldby Orange","Purple/Orange","Blue/Tan","Green/Purple","Knoza 00","Knoza 18","Calpan", +"Calbayo","Fib53","Purple/Orange","PMH","Konjo 08","Konikyo","McCahon","Pills","Pink/Yellow/Orange","Autumn 04", +"Autumn 02","Candide","Chic","Coffee","Emerald Dragon 01","Landscape 57","Landscape 22","Landscape 47","Landscape 10","Landscape 76", +"Landscape 61","Landscape 60","Landscape 51","Landscape 06","Ocean Breeze 49","Ocean Breeze 57","Ocean Breeze 74","Pink Splash 05","Pink Splash 10","Vintage 56", +"Vintage 10","Gold/Yellow","Radioactive Slime","Pastel Rainbow","Purple Sunset","Adrift in Dreams","Set3","Pastel1","Es Rosa","Daybreak", +"Melancholiy","Xanidu","Air","Revolution","Sky05","Sky33","Sky45","Carousel","NRWC" ])====="; diff --git a/wled00/const.h b/wled00/const.h index e345100c82..8feff1068f 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 103 // custom palette.h +#define GRADIENT_PALETTE_COUNT 116 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" diff --git a/wled00/palettes.h b/wled00/palettes.h index e664bfd9ce..42f2ffd2d6 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -1984,22 +1984,241 @@ DEFINE_PALETTE( janico_22_gp ) { 255, 194,225,255}; +// Gradient palette "Adrift_in_Dreams_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/colo/Skyblue2u/tn/Adrift_in_Dreams.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 40 bytes of program space. + +DEFINE_PALETTE( Adrift_in_Dreams_gp ) { + 0, 148,223, 77, + 51, 148,223, 77, + 51, 86,182, 89, + 102, 86,182, 89, + 102, 36,131, 72, + 153, 36,131, 72, + 153, 5, 61, 51, + 204, 5, 61, 51, + 204, 1, 15, 29, + 255, 1, 15, 29}; + + +// Gradient palette "Set3_03_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/cb/qual/tn/Set3_03.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 24 bytes of program space. + +DEFINE_PALETTE( Set3_03_gp ) { + 0, 54,168,137, + 84, 54,168,137, + 84, 255,255,105, + 170, 255,255,105, + 170, 118,127,172, + 255, 118,127,172}; + +// Gradient palette "Pastel1_06_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/cb/qual/tn/Pastel1_06.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 48 bytes of program space. + +DEFINE_PALETTE( Pastel1_06_gp ) { + 0, 244,118, 98, + 42, 244,118, 98, + 42, 101,157,190, + 84, 101,157,190, + 84, 142,213,133, + 127, 142,213,133, + 127, 177,154,192, + 170, 177,154,192, + 170, 252,178, 87, + 212, 252,178, 87, + 212, 255,255,145, + 255, 255,255,145}; + + +// Gradient palette "es_rosa_55_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rosa/tn/es_rosa_55.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( es_rosa_55_gp ) { + 0, 6, 1, 2, + 101, 54, 1, 10, + 170, 15, 29, 4, + 216, 95,124, 54, + 255, 213,233,158}; + +// Gradient palette "daybreak_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/1/tn/daybreak.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 32 bytes of program space. + +DEFINE_PALETTE( daybreak_gp ) { + 0, 1, 1, 1, + 91, 4, 11, 21, + 140, 11, 31,135, + 150, 255,255,125, + 165, 132, 18,123, + 198, 58, 92,221, + 232, 57,168,223, + 255, 255,241,242}; + + +// Gradient palette "melancholiy_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/3/tn/melancholiy.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( melancholiy_gp ) { + 0, 255,171,242, + 76, 1, 2,105, + 140, 121,136,125, + 211, 255,171,242, + 255, 1, 2,105}; + + +// Gradient palette "xanidu_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/3/tn/xanidu.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. + +DEFINE_PALETTE( xanidu_gp ) { + 0, 118,161,226, + 5, 255,255, 45, + 15, 252,203,156, + 53, 79, 1,162, + 94, 67, 1, 7, + 132, 1, 55,156, + 173, 1,127, 61, + 211, 39, 45, 72, + 255, 118,161,226}; + + +// Gradient palette "air_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/3/tn/air.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 36 bytes of program space. + +DEFINE_PALETTE( air_gp ) { + 0, 252,246,103, + 84, 252,246,103, + 140, 14, 1, 91, + 155, 165,176,156, + 163, 252,246,103, + 170, 14, 1, 91, + 181, 165,176,156, + 193, 252,246,103, + 255, 252,246,103}; + + +// Gradient palette "revolution2_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/pj/6/tn/revolution2.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 40 bytes of program space. + +DEFINE_PALETTE( revolution2_gp ) { + 0, 112, 46, 21, + 33, 101, 69, 14, + 61, 194, 74, 29, + 91, 242,115, 52, + 119, 215,211,102, + 145, 2, 2, 1, + 163, 8, 28, 46, + 186, 17, 9, 1, + 229, 215,211,102, + 255, 242,115, 52}; + + +// Gradient palette "sky_33_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rafi/tn/sky-33.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( sky_33_gp ) { + 0, 237,229,140, + 51, 227,107, 79, + 87, 155, 55, 54, + 178, 22, 28, 36, + 255, 5, 19, 31}; + +// Gradient palette "sky_45_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rafi/tn/sky-45.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 20 bytes of program space. + +DEFINE_PALETTE( sky_45_gp ) { + 0, 249,205, 4, + 51, 255,239,123, + 87, 5,141, 85, + 178, 1, 26, 43, + 255, 0, 2, 23}; + +// Gradient palette "sky_05_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rafi/tn/sky-05.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( sky_05_gp ) { + 0, 252, 61, 2, + 25, 255,146, 4, + 63, 224,255,255, + 101, 46,114,226, + 127, 6, 40,127, + 191, 1, 3, 17, + 255, 1, 1, 4}; + + +// Gradient palette "carousel_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/carousel.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 28 bytes of program space. + +DEFINE_PALETTE( carousel_gp ) { + 0, 2, 6, 37, + 101, 2, 6, 37, + 122, 177,121, 9, + 127, 217,149, 2, + 132, 177,121, 9, + 153, 84, 13, 36, + 255, 84, 13, 36}; + +// Gradient palette "nrwc_gp", originally from +// http://soliton.vm.bytemark.co.uk/pub/cpt-city/wkp/tubs/tn/nrwc.png.index.html +// converted for FastLED with gammas (2.6, 2.2, 2.5) +// Size: 44 bytes of program space. + +DEFINE_PALETTE( nrwc_gp ) { + 0, 1, 1, 1, + 25, 4, 8, 1, + 51, 1, 11, 2, + 76, 4, 36, 9, + 102, 6, 66, 18, + 127, 27, 95, 23, + 153, 82,127, 31, + 178, 197,171, 40, + 204, 133,100, 19, + 229, 97, 48, 6, + 255, 163, 55, 7}; + + + // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly // by name every time. const byte* const gGradientPalettes[] PROGMEM = { - ib_jul01_gp, //13-00 Jul01 + // starts at #13: + ib_jul01_gp, es_vintage_57_gp, es_vintage_01_gp, es_rivendell_15_gp, rgi_15_gp, retro2_16_gp, Analogous_1_gp, + + // 20 es_pinksplash_08_gp, es_pinksplash_07_gp, Coral_reef_gp, - es_ocean_breeze_068_gp, es_ocean_breeze_036_gp, departure_gp, @@ -2007,10 +2226,11 @@ const byte* const gGradientPalettes[] PROGMEM = { es_landscape_33_gp, rainbowsherbet_gp, gr65_hult_gp, + + // 30 gr64_hult_gp, GMT_drywet_gp, ib15_gp, - Fuschia_7_gp, es_emerald_dragon_08_gp, lava_gp, @@ -2018,10 +2238,11 @@ const byte* const gGradientPalettes[] PROGMEM = { haiyan_23_gp, Colorfull_gp, Magenta_Evening_gp, + + // 40 Pink_Purple_gp, Sunset_Real_gp, es_autumn_19_gp, - BlacK_Blue_Magenta_White_gp, BlacK_Magenta_Red_gp, BlacK_Red_Magenta_Yellow_gp, @@ -2029,10 +2250,11 @@ const byte* const gGradientPalettes[] PROGMEM = { Sunset_Yellow_gp, cloud_gp, fireandice_gp, + + // 50 bhw2_39_gp, rainfall_gp, tashangel_gp, - butterflytalker_gp, os250k_metres_gp, Night_Midnight_gp, @@ -2040,8 +2262,9 @@ const byte* const gGradientPalettes[] PROGMEM = { BlueSky_gp, Gold_Orange_gp, frizzell_10_gp, + + // 60 frizzell_12_gp, - fib53_18_gp, fib53_13_gp, fib53_17_gp, @@ -2050,8 +2273,9 @@ const byte* const gGradientPalettes[] PROGMEM = { Analogous_04a_gp, Cyan_Orange_Stripped_gp, Cyan_White_Green_gp, - Wild_Orange_gp, + + // 70 IKat_Radial_gp, Citrus_gp, Teal_Blue_gp, @@ -2061,8 +2285,9 @@ const byte* const gGradientPalettes[] PROGMEM = { green_purple_d07_gp, knoza_00_gp, knoza_18_gp, - calpan_18_gp, + + // 80 calbayo_18_gp, fib53_15_gp, purple_orange_d08_gp, @@ -2073,6 +2298,8 @@ const byte* const gGradientPalettes[] PROGMEM = { Pills_2_gp, Pink_Yellow_Orange_1_gp, es_autumn_04_gp, + + // 90 es_autumn_02_gp, es_candide_30_gp, es_chic_16_gp, @@ -2083,6 +2310,8 @@ const byte* const gGradientPalettes[] PROGMEM = { es_landscape_47_gp, es_landscape_10_gp, es_landscape_76_gp, + + // 100 es_landscape_61_gp, es_landscape_60_gp, es_landscape_51_gp, @@ -2093,12 +2322,29 @@ const byte* const gGradientPalettes[] PROGMEM = { es_pinksplash_05_gp, es_pinksplash_10_gp, es_vintage_56_gp, + + // 110 es_vintage_10_gp, gold_yellow_gp, radioactive_slime_gp, pastel_rainbow_gp, purple_sunset_gp, - janico_22_gp + Adrift_in_Dreams_gp, + Set3_03_gp, + Pastel1_06_gp, + es_rosa_55_gp, + daybreak_gp, + + // 120 + melancholiy_gp, + xanidu_gp, + air_gp, + revolution2_gp, + sky_05_gp, + sky_33_gp, + sky_45_gp, + carousel_gp, + nrwc_gp, /* Sunset_Real_gp, //13-00 Sunset @@ -2162,7 +2408,7 @@ const byte* const gGradientPalettes[] PROGMEM = { */ }; const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); -#define GRADIENT_PALETTE_COUNT 103 +#define GRADIENT_PALETTE_COUNT 116 #endif From 2503f8c24b55920046d060a93e09ddd2ece95329 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 18 Aug 2022 22:35:41 -0700 Subject: [PATCH 156/263] Auto-updating from a remote server --- usermods/Tubes/controller.h | 36 ++++- usermods/Tubes/led_strip.h | 17 +- usermods/Tubes/node.h | 5 +- usermods/Tubes/update_server.py | 275 ++++++++++++++++++++++++++++++++ usermods/Tubes/updater.h | 221 ++++++++++++++++++++----- 5 files changed, 496 insertions(+), 58 deletions(-) create mode 100644 usermods/Tubes/update_server.py diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 66c937436e..6190af07d9 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -70,7 +70,7 @@ class Button { class PatternController : public MessageReceiver { public: - const static int FRAMES_PER_SECOND = 100; // how often we animate, in frames per second + const static int FRAMES_PER_SECOND = 60; // how often we animate, in frames per second const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds uint8_t num_leds; @@ -79,6 +79,8 @@ class PatternController : public MessageReceiver { bool isMaster = false; uint16_t wled_fader = 0; + AutoUpdater auto_updater; + Timer graphicsTimer; Timer updateTimer; @@ -233,6 +235,7 @@ class PatternController : public MessageReceiver { this->send_update(); } + this->auto_updater.update(); } #ifdef USELCD @@ -280,6 +283,34 @@ class PatternController : public MessageReceiver { // Draw effects layers over whatever WLED is doing. this->effects->draw(&strip); + + CRGB c; + switch (this->auto_updater.status) { + case Started: + case Connected: + case Received: + c = CRGB::Yellow; + if (millis() % 1000 < 500) { + c = CRGB::Black; + } + break; + + case Failed: + c = CRGB::Red; + break; + + case Complete: + c = CRGB::Green; + break; + + case Idle: + default: + return; + } + for (int i = 0; i < 20; i++) { + strip.setPixelColor(i, c); + } + } void restart_phrase() { @@ -715,10 +746,11 @@ class PatternController : public MessageReceiver { Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); Serial.println("@ - power save mode"); + Serial.println("U - begin auto-update"); return; case 'U': - WifiUpdater().web_update(); + this->auto_updater.start(); return; case 0: diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index a4aca410c5..a15ddfaf47 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -11,8 +11,8 @@ class LEDs { CRGB leds[MAX_REAL_LEDS]; // CRGB led_array[MAX_REAL_LEDS]; - const static int FRAMES_PER_SECOND = 150; // how often we refresh the strip, in frames per second - const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we refresh the strip, in milliseconds + const static int TARGET_FRAMES_PER_SECOND = 150; + const static int TARGET_REFRESH = 1000 / TARGET_FRAMES_PER_SECOND; int num_leds; uint16_t fps = 0; @@ -33,21 +33,12 @@ class LEDs { } } - void show() { - // There's nothing to do right now, because in Tubes.h we blend the LEDs into - } - void update(bool reverse=false) { - EVERY_N_MILLISECONDS( this->REFRESH_PERIOD ) { - // Update the LEDs - if (reverse) - this->reverse(); - show(); + EVERY_N_MILLISECONDS( this->TARGET_REFRESH ) { this->fps++; } - EVERY_N_MILLISECONDS( 1000 ) { - if (this->fps < (FRAMES_PER_SECOND - 30)) { + if (this->fps < (TARGET_FRAMES_PER_SECOND - 30)) { Serial.print(this->fps); Serial.println((char *)F(" fps!")); } diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 15e2235310..d2a0bb170d 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -50,7 +50,7 @@ typedef struct { void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); -char *command_name(CommandId command) { +const char *command_name(CommandId command) { switch (command) { case COMMAND_UPDATE: return "UPDATE"; @@ -130,7 +130,10 @@ class LightNode { void configure_ap() { // Don't connect to any networks. + // strcpy(clientSSID, "Fish Tank"); + // strcpy(clientPass, "Fish Tank"); strcpy(clientSSID, ""); + strcpy(clientPass, ""); // Try to hide the access point unless this is the "root" node if (this->is_following()) { diff --git a/usermods/Tubes/update_server.py b/usermods/Tubes/update_server.py new file mode 100644 index 0000000000..3bb1e4d624 --- /dev/null +++ b/usermods/Tubes/update_server.py @@ -0,0 +1,275 @@ +#!/usr/bin/python +"""Simple HTTP Server. + +This module builds on BaseHTTPServer by implementing the standard GET +and HEAD requests in a fairly straightforward manner. + +""" +__version__ = "0.6" + +__all__ = ["SimpleHTTPRequestHandler"] + +import os +import posixpath +import BaseHTTPServer +import urllib +import cgi +import sys +import mimetypes +import zlib +from optparse import OptionParser + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +SERVER_PORT = 8000 +encoding_type = 'gzip' + +def parse_options(): + # Option parsing logic. + parser = OptionParser() + parser.add_option("-e", "--encoding", dest="encoding_type", + help="Encoding type for server to utilize", + metavar="ENCODING", default='gzip') + global SERVER_PORT + parser.add_option("-p", "--port", dest="port", default=SERVER_PORT, + help="The port to serve the files on", + metavar="ENCODING") + (options, args) = parser.parse_args() + global encoding_type + encoding_type = options.encoding_type + SERVER_PORT = int(options.port) + + if encoding_type not in ['zlib', 'deflate', 'gzip']: + sys.stderr.write("Please provide a valid encoding_type for the server to utilize.\n") + sys.stderr.write("Possible values are 'zlib', 'gzip', and 'deflate'\n") + sys.stderr.write("Usage: python GzipSimpleHTTPServer.py --encoding=\n") + sys.exit() + + +def zlib_encode(content): + zlib_compress = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS) + data = zlib_compress.compress(content) + zlib_compress.flush() + return data + + +def deflate_encode(content): + deflate_compress = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS) + data = deflate_compress.compress(content) + deflate_compress.flush() + return data + + +def gzip_encode(content): + gzip_compress = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) + data = gzip_compress.compress(content) + gzip_compress.flush() + return data + + +class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Simple HTTP request handler with GET and HEAD commands. + + This serves files from the current directory and any of its + subdirectories. The MIME type for files is determined by + calling the .guess_type() method. + + The GET and HEAD requests are identical except that the HEAD + request omits the actual contents of the file. + + """ + + server_version = "SimpleHTTP/" + __version__ + + def do_GET(self): + """Serve a GET request.""" + content = self.send_head() + if content: + self.wfile.write(content) + + def do_HEAD(self): + """Serve a HEAD request.""" + content = self.send_head() + + def send_head(self): + """Common code for GET and HEAD commands. + + This sends the response code and MIME headers. + + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + + """ + path = self.translate_path(self.path) + print("Serving path '%s'" % path) + f = None + if os.path.isdir(path): + if not self.path.endswith('/'): + # redirect browser - doing basically what apache does + self.send_response(301) + self.send_header("Location", self.path + "/") + self.end_headers() + return None + for index in "index.html", "index.htm": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return self.list_directory(path).read() + ctype = self.guess_type(path) + try: + # Always read in binary mode. Opening files in text mode may cause + # newline translations, making the actual size of the content + # transmitted *less* than the content-length! + f = open(path, 'rb') + except IOError: + self.send_error(404, "File not found") + return None + self.send_response(200) + self.send_header("Content-type", ctype) + self.send_header("Content-Encoding", encoding_type) + fs = os.fstat(f.fileno()) + raw_content_length = fs[6] + content = f.read() + + # # Encode content based on runtime arg + # if encoding_type == "gzip": + # content = gzip_encode(content) + # elif encoding_type == "deflate": + # content = deflate_encode(content) + # elif encoding_type == "zlib": + # content = zlib_encode(content) + + compressed_content_length = len(content) + f.close() + self.send_header("Content-Length", max(raw_content_length, compressed_content_length)) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.end_headers() + return content + + def list_directory(self, path): + """Helper to produce a directory listing (absent index.html). + + Return value is either a file object, or None (indicating an + error). In either case, the headers are sent, making the + interface the same as for send_head(). + + """ + try: + list = os.listdir(path) + except os.error: + self.send_error(404, "No permission to list directory") + return None + list.sort(key=lambda a: a.lower()) + f = StringIO() + displaypath = cgi.escape(urllib.unquote(self.path)) + f.write('') + f.write("\nDirectory listing for %s\n" % displaypath) + f.write("\n

Directory listing for %s

\n" % displaypath) + f.write("
\n
    \n") + for name in list: + fullname = os.path.join(path, name) + displayname = linkname = name + # Append / for directories or @ for symbolic links + if os.path.isdir(fullname): + displayname = name + "/" + linkname = name + "/" + if os.path.islink(fullname): + displayname = name + "@" + # Note: a link to a directory displays with @ and links with / + f.write('
  • %s\n' + % (urllib.quote(linkname), cgi.escape(displayname))) + f.write("
\n
\n\n\n") + length = f.tell() + f.seek(0) + self.send_response(200) + encoding = sys.getfilesystemencoding() + self.send_header("Content-type", "text/html; charset=%s" % encoding) + self.send_header("Content-Length", str(length)) + self.end_headers() + return f + + def translate_path(self, path): + """Translate a /-separated PATH to the local filename syntax. + + Components that mean special things to the local file system + (e.g. drive or directory names) are ignored. (XXX They should + probably be diagnosed.) + + """ + # abandon query parameters + path = path.split('?',1)[0] + path = path.split('#',1)[0] + path = posixpath.normpath(urllib.unquote(path)) + words = path.split('/') + words = filter(None, words) + path = os.getcwd() + "/../../.pio/build/esp32_quinled_uno" + for word in words: + drive, word = os.path.splitdrive(word) + head, word = os.path.split(word) + if word in (os.curdir, os.pardir): continue + path = os.path.join(path, word) + return path + + def guess_type(self, path): + """Guess the type of a file. + + Argument is a PATH (a filename). + + Return value is a string of the form type/subtype, + usable for a MIME Content-type header. + + The default implementation looks the file's extension + up in the table self.extensions_map, using application/octet-stream + as a default; however it would be permissible (if + slow) to look inside the data to make a better guess. + + """ + + base, ext = posixpath.splitext(path) + if ext in self.extensions_map: + return self.extensions_map[ext] + ext = ext.lower() + if ext in self.extensions_map: + return self.extensions_map[ext] + else: + return self.extensions_map[''] + + if not mimetypes.inited: + mimetypes.init() # try to read system mime.types + extensions_map = mimetypes.types_map.copy() + extensions_map.update({ + '': 'application/octet-stream', # Default + '.py': 'text/plain', + '.c': 'text/plain', + '.h': 'text/plain', + }) + + +def test(HandlerClass = SimpleHTTPRequestHandler, + ServerClass = BaseHTTPServer.HTTPServer): + """Run the HTTP request handler class. + + This runs an HTTP server on port 8000 (or the first command line + argument). + + """ + + parse_options() + + server_address = ('0.0.0.0', SERVER_PORT) + + SimpleHTTPRequestHandler.protocol_version = "HTTP/1.0" + httpd = BaseHTTPServer.HTTPServer(server_address, SimpleHTTPRequestHandler) + + sa = httpd.socket.getsockname() + print "Serving HTTP on", sa[0], "port", sa[1], "..." + httpd.serve_forever() + BaseHTTPServer.test(HandlerClass, ServerClass) + + +if __name__ == '__main__': + test() \ No newline at end of file diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 4a2a3927a2..c8295e8ffd 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -1,52 +1,160 @@ #pragma once +#include "wled.h" #include #include #include +#include "timer.h" // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { return header.substring(strlen(headerName.c_str())); } -class WifiUpdater { +typedef enum UpdateWorkflowStatus: uint8_t { + Idle=0, + Started=1, + Connected=2, + Received=4, + Complete=100, + Failed=101, +} UpdateWorkflowStatus; + +class AutoUpdater { public: - String host = "kwater.kelectronics.net"; - String bin = "/api/getfirmware/firmwareLarge.bin"; - int port = 80; + String autoUpdateSSID = ">"; + String autoUpdatePass = ">"; + String host = ""; + String bin = "/firmware.bin"; + int port = 8000; + long fileSize = 0; - void web_update() { - WiFiClient client; - long fileSize = 0; - String contentType = ""; + String _storedSSID = ""; + String _storedPass = ""; + + int progress = 0; + + WiFiClient _client; + UpdateWorkflowStatus status = Idle; + Timer timeoutTimer; + Timer progressTimer; + + void update() { + switch (this->status) { + case Complete: + case Failed: + if (this->progressTimer.ended()) + this->status = Idle; + case Idle: + return; + + case Started: + this->do_connect(); + return; - if (!WiFi.isConnected()) { - Serial.println("Not on WiFi"); + case Connected: + this->do_request(this->_client); + return; + } + } + + void start() { + if (this->status != Idle) { + log("update already in progress."); return; } - Serial.println("Connecting"); + // The auto-update process might break the current connection + _storedSSID = String(clientSSID); + _storedPass = String(clientPass); + + WLED::instance().disableWatchdog(); + + log("starting autoupdate"); + this->status = Started; + } + + void stop() { + this->_client.stop(); + + strcpy(clientSSID, _storedSSID.c_str()); + strcpy(clientPass, _storedPass.c_str()); + WLED::instance().enableWatchdog(); + + this->status = Idle; + } + + private: + void log(const char *message) { + Serial.printf("OTA: %s\n", message); + } + + void abort(const char *message) { + log(message); + this->status = Failed; + this->progressTimer.start(10000); + this->stop(); + } + + void do_connect() { + auto s = WiFi.status(); + switch (s) { + case WL_DISCONNECTED: + log("connecting to autoupdate server"); + strcpy(clientSSID, autoUpdateSSID.c_str()); + strcpy(clientPass, autoUpdatePass.c_str()); + return; + + case WL_NO_SSID_AVAIL: + abort("Invalid auto-update SSID"); + return; + + case WL_CONNECT_FAILED: + abort("Invalid auto-update password"); + return; + + case WL_CONNECTED: + if (WiFi.SSID() != autoUpdateSSID) { + log("disconnecting from WiFi"); + WiFi.disconnect(true); + apBehavior = AP_BEHAVIOR_BUTTON_ONLY; + return; + } + + log("WiFi connected"); + this->status = Connected; + return; + + default: + Serial.printf("OTA: wifi %d", (int)s); + break; + } + } + + void do_request(WiFiClient client) { if (!client.connect(host.c_str(), port)) { - Serial.println("Connect failed"); - client.stop(); + log("connect failed"); + this->stop(); return; } + log("connected"); + // Get the contents of the bin file client.print(String("GET ") + bin + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + - "Cache-Control: no-cache\r\n" + - "Connection: close\r\n\r\n"); - - unsigned long timeout = millis(); - while (client.available() == 0) { - if (millis() - timeout > 5000) { - Serial.println("Client Timeout !"); - client.stop(); + "Cache-Control: no-cache\r\n\r\n"); + + timeoutTimer.start(5000); + while (!client.available()) { + vTaskDelay( 200 ); + if (timeoutTimer.ended()) { + abort("timed out waiting for response"); return; } } - Serial.println("Reading headers"); + String contentType = ""; + while (client.available()) { // read line till /n - if the line is empty, it's the end of the headers. String line = client.readStringUntil('\n'); @@ -56,8 +164,7 @@ class WifiUpdater { // Check if the HTTP Response is 200 if (line.startsWith("HTTP/")) { if (line.indexOf("200") < 0) { - Serial.println("Got a non 200 status code"); - client.flush(); + abort(line.c_str()); return; } } @@ -65,40 +172,70 @@ class WifiUpdater { // Read the file size from Content-Length if (line.startsWith("Content-Length: ")) { fileSize = atol((getHeaderValue(line, "Content-Length: ")).c_str()); - Serial.println(line); + log(line.c_str()); } // Read the content type from Content-Type if (line.startsWith("Content-Type: ")) { contentType = getHeaderValue(line, "Content-Type: "); - Serial.println(line); + log(line.c_str()); + } + if (line.startsWith("Content-type: ")) { + contentType = getHeaderValue(line, "Content-type: "); + log(line.c_str()); } + } if (fileSize == 0 || contentType != "application/octet-stream") { - Serial.println("Must get a valid Content-Type and Content-Length header."); - client.flush(); + abort("Must get a valid Content-Type and Content-Length header."); return; } - - Serial.println("Beginning update"); + + log("found a valid OTA BIN"); + + this->status = Received; + this->do_update(client); + } + + void do_update(WiFiClient client) { if (!Update.begin(fileSize)) { - Serial.println("Cannot do the update"); + abort("no room for the update"); return; }; - Update.writeStream(client); - if (!Update.end()) { - Serial.println("Error Occurred. Error #: " + String(Update.getError())); + + this->progress = 0; + vTaskDelay(500); + uint8_t buf[4096]; + int lr; + while ((lr = client.read(buf, sizeof(buf))) > 0) { + size_t written = Update.write(buf, lr); + if (!written) + break; + + this->progress += written; + Serial.printf(" %d of %ld\n", this->progress, fileSize); + + // Give the server time to send some more data + if (!client.available()) + vTaskDelay(100); } - if (Update.isFinished()) { - Serial.println("Update successfully completed. Rebooting."); - ESP.restart(); - } else { - Serial.println("Update not finished? Something went wrong!"); + if (!Update.end()) { + Serial.println("Error #: " + String(Update.getError())); + abort("Error during streaming"); + return; } - client.flush(); - return; + if (!Update.isFinished()) { + abort("Error during finishing"); + return; + } + + log("update successfully completed. Rebooting."); + doReboot = true; + this->status = Complete; + this->progressTimer.start(10000); + this->stop(); } -}; \ No newline at end of file +}; From 131f0a43b849d344746cf09950a8169e604c86b5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 19 Aug 2022 23:30:14 -0700 Subject: [PATCH 157/263] misc fixes --- usermods/Tubes/controller.h | 25 ++++++++++++- usermods/Tubes/debug.h | 13 ++++++- usermods/Tubes/global_state.h | 1 + usermods/Tubes/node.h | 7 ++-- usermods/Tubes/pattern.h | 4 +-- usermods/Tubes/updater.h | 66 +++++++++++++++++++++-------------- 6 files changed, 83 insertions(+), 33 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 6190af07d9..38f968de8e 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -79,7 +79,7 @@ class PatternController : public MessageReceiver { bool isMaster = false; uint16_t wled_fader = 0; - AutoUpdater auto_updater; + AutoUpdater auto_updater = AutoUpdater(); Timer graphicsTimer; Timer updateTimer; @@ -732,6 +732,14 @@ class PatternController : public MessageReceiver { addGlitter(); return; + case 'W': + // Clear wifi + Serial.print("Clearing WiFi connection."); + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + WiFi.disconnect(false, true); + return; + case '?': Serial.println(F("b###.# - set bpm")); Serial.println(F("s - start phrase")); @@ -747,12 +755,17 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); Serial.println("@ - power save mode"); Serial.println("U - begin auto-update"); + Serial.println("O - offer an auto-update"); return; case 'U': this->auto_updater.start(); return; + case 'O': + broadcast_autoupdate(); + return; + case 0: // Empty command return; @@ -780,6 +793,11 @@ class PatternController : public MessageReceiver { this->node->sendCommand(COMMAND_OPTIONS, &this->options, sizeof(this->options)); } + void broadcast_autoupdate() { + AutoUpdateOffer offer; + this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.location, sizeof(this->auto_updater.location)); + } + virtual void onCommand(CommandId command, void *data) { switch (command) { case COMMAND_RESET: @@ -811,6 +829,11 @@ class PatternController : public MessageReceiver { this->beats->sync(state.bpm, state.beat_frame); return; } + + case COMMAND_UPGRADE: + memcpy((byte*)&this->auto_updater.location, (byte*)data, sizeof(AutoUpdateOffer)); + this->auto_updater.start(); + return; } Serial.printf("UNKNOWN COMMAND %02X", command); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index d50dc42ff3..4bea94a1e8 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -82,7 +82,7 @@ class DebugController { auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); - Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n\n", + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n", mode_name, seg.mode, palette_name, @@ -91,6 +91,17 @@ class DebugController { seg.intensity, this->controller->wled_fader ); + + Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", + this->controller->auto_updater.location.version, + this->controller->auto_updater.status, + this->controller->auto_updater.location.ssid, + this->controller->auto_updater.location.host[0], + this->controller->auto_updater.location.ssid[1], + this->controller->auto_updater.location.ssid[2], + this->controller->auto_updater.location.ssid[3] + ); + } } diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index a19646ec09..0e1af1c008 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,4 +57,5 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_UPGRADE = 0xE0; const static CommandId COMMAND_RESET = 0xF0; \ No newline at end of file diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index d2a0bb170d..81b5f01ed3 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -38,7 +38,7 @@ typedef enum{ ROOT=1, } MessageRecipients; -#define MESSAGE_DATA_SIZE 50 +#define MESSAGE_DATA_SIZE 64 typedef struct { MeshNodeHeader header; @@ -279,7 +279,7 @@ class LightNode { return; message->timebase = strip.timebase + millis(); -#ifdef NODE_DEBUGGING +#ifdef NODE_DEBUGGING Serial.print(" <<< "); print_message(message, 0); Serial.println(); @@ -295,7 +295,8 @@ class LightNode { void sendCommand(CommandId command, void *data, uint8_t len) { if (len > MESSAGE_DATA_SIZE) { - Serial.println("Message is too big!"); + Serial.printf("Message is too big: %d vs %d\n", + len, MESSAGE_DATA_SIZE); return; } diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 14946808bc..1e89b94f38 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -199,7 +199,7 @@ PatternDef gPatterns[] = { {0, bpm_palette, {ShortDuration}}, {0, bpm_palette, {MediumDuration, HighEnergy}}, {FX_MODE_FADE, draw_wled_fx, {ShortDuration, Boring}}, // 12 - {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration}}, // 30 + {FX_MODE_CHASE_RAINBOW, draw_wled_fx, {MediumDuration, HighEnergy}}, // 30 // Make it HighEnergy? or find out why it's sometimes flashy {FX_MODE_AURORA, draw_wled_fx, {MediumDuration, Boring}}, // 38 // TODO: Aurora is too dark? @@ -216,7 +216,7 @@ PatternDef gPatterns[] = { {FX_MODE_FILLNOISE8, draw_wled_fx, {LongDuration}}, // 69 {FX_MODE_NOISE16_2, draw_wled_fx, {MediumDuration}}, // 71 {FX_MODE_NOISE16_3, draw_wled_fx, {ShortDuration}}, // 72 - {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration}}, // 72 + {FX_MODE_NOISE16_3, draw_wled_fx, {LongDuration, MediumEnergy}}, // 72 // TODO: Noise3 needs to be slowed down, it's a bit spastic {FX_MODE_COLORTWINKLE, draw_wled_fx, {MediumDuration}}, // 74 diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index c8295e8ffd..6d475cb969 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,6 +6,8 @@ #include #include "timer.h" +#define RELEASE_VERSION 3 + // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { return header.substring(strlen(headerName.c_str())); @@ -20,13 +22,19 @@ typedef enum UpdateWorkflowStatus: uint8_t { Failed=101, } UpdateWorkflowStatus; +typedef struct AutoUpdateOffer { + int version = RELEASE_VERSION; + char ssid[25] = "Fish Tank"; + char password[25] = "Fish Tank"; + IPAddress host = IPAddress(192,168,0,146); +} AutoUpdateOffer; + class AutoUpdater { public: - String autoUpdateSSID = ">"; - String autoUpdatePass = ">"; - String host = ""; - String bin = "/firmware.bin"; + AutoUpdateOffer location; + String path = "/firmware.bin"; int port = 8000; + long fileSize = 0; String _storedSSID = ""; @@ -46,6 +54,7 @@ class AutoUpdater { if (this->progressTimer.ended()) this->status = Idle; case Idle: + case Received: return; case Started: @@ -53,7 +62,7 @@ class AutoUpdater { return; case Connected: - this->do_request(this->_client); + this->do_request(); return; } } @@ -67,7 +76,6 @@ class AutoUpdater { // The auto-update process might break the current connection _storedSSID = String(clientSSID); _storedPass = String(clientPass); - WLED::instance().disableWatchdog(); log("starting autoupdate"); @@ -76,11 +84,10 @@ class AutoUpdater { void stop() { this->_client.stop(); - strcpy(clientSSID, _storedSSID.c_str()); strcpy(clientPass, _storedPass.c_str()); + WiFi.disconnect(false, true); WLED::instance().enableWatchdog(); - this->status = Idle; } @@ -89,6 +96,10 @@ class AutoUpdater { Serial.printf("OTA: %s\n", message); } + void log(String message) { + log(message.c_str()); + } + void abort(const char *message) { log(message); this->status = Failed; @@ -101,8 +112,8 @@ class AutoUpdater { switch (s) { case WL_DISCONNECTED: log("connecting to autoupdate server"); - strcpy(clientSSID, autoUpdateSSID.c_str()); - strcpy(clientPass, autoUpdatePass.c_str()); + strcpy(clientSSID, this->location.ssid); + strcpy(clientPass, this->location.password); return; case WL_NO_SSID_AVAIL: @@ -114,9 +125,9 @@ class AutoUpdater { return; case WL_CONNECTED: - if (WiFi.SSID() != autoUpdateSSID) { + if (WiFi.SSID() != String(this->location.ssid)) { log("disconnecting from WiFi"); - WiFi.disconnect(true); + WiFi.disconnect(false, true); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; return; } @@ -131,33 +142,36 @@ class AutoUpdater { } } - void do_request(WiFiClient client) { - if (!client.connect(host.c_str(), port)) { - log("connect failed"); - this->stop(); + void do_request() { + log("connecting"); + if (!this->_client.connect(this->location.host, this->port)) { + abort("connect failed"); return; } - log("connected"); // Get the contents of the bin file - client.print(String("GET ") + bin + " HTTP/1.1\r\n" + - "Host: " + host + "\r\n" + + log("requesting update package"); + this->_client.print(String("GET ") + this->path + " HTTP/1.1\r\n" + + "Host: " + this->location.host + "\r\n" + "Cache-Control: no-cache\r\n\r\n"); + log("awaiting response"); timeoutTimer.start(5000); - while (!client.available()) { - vTaskDelay( 200 ); + while (!this->_client.available()) { + vTaskDelay( 400 ); if (timeoutTimer.ended()) { abort("timed out waiting for response"); return; } + log("waiting..."); } String contentType = ""; - while (client.available()) { + log("reading response"); + while (this->_client.available()) { // read line till /n - if the line is empty, it's the end of the headers. - String line = client.readStringUntil('\n'); + String line = this->_client.readStringUntil('\n'); line.trim(); if (!line.length()) break; @@ -195,7 +209,7 @@ class AutoUpdater { log("found a valid OTA BIN"); this->status = Received; - this->do_update(client); + this->do_update(this->_client); } void do_update(WiFiClient client) { @@ -206,7 +220,7 @@ class AutoUpdater { this->progress = 0; vTaskDelay(500); - uint8_t buf[4096]; + uint8_t buf[2048]; int lr; while ((lr = client.read(buf, sizeof(buf))) > 0) { size_t written = Update.write(buf, lr); @@ -232,8 +246,8 @@ class AutoUpdater { return; } - log("update successfully completed. Rebooting."); doReboot = true; + log("update successfully completed. Rebooting."); this->status = Complete; this->progressTimer.start(10000); this->stop(); From 953d8b7d1f6732720744ae09c8430401e84d018d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 20 Aug 2022 22:17:20 -0700 Subject: [PATCH 158/263] Manual mode and basic settings --- usermods/Tubes/controller.h | 106 +++++++++++++++++++++++++-------- usermods/Tubes/debug.h | 14 ++++- usermods/Tubes/node.h | 7 ++- usermods/Tubes/pattern.h | 4 ++ usermods/Tubes/virtual_strip.h | 16 +++-- 5 files changed, 110 insertions(+), 37 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 38f968de8e..77090ef167 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -9,10 +9,12 @@ #include "pattern.h" #include "palettes.h" #include "effects.h" +#include "led_strip.h" #include "global_state.h" #include "node.h" const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; +const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 @@ -77,12 +79,16 @@ class PatternController : public MessageReceiver { VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; bool isMaster = false; + uint8_t paletteOverride = 0; + uint8_t patternOverride = 0; uint16_t wled_fader = 0; AutoUpdater auto_updater = AutoUpdater(); Timer graphicsTimer; Timer updateTimer; + Timer paletteOverrideTimer; + Timer patternOverrideTimer; #ifdef USELCD Lcd *lcd; @@ -126,9 +132,12 @@ class PatternController : public MessageReceiver { { this->node->setup(); this->isMaster = isMaster; - this->options.power_save = false; + this->options.power_save = true; this->options.debugging = false; - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + if (isMaster) + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + else + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; this->load_options(this->options); #ifdef USELCD @@ -140,7 +149,8 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; - VirtualStrip::set_wled_pattern(DEFAULT_WLED_FX, 128, 128); + VirtualStrip::set_wled_palette(0); // Default palette + VirtualStrip::set_wled_pattern(0, 128, 128); // Default pattern this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); @@ -197,30 +207,56 @@ class PatternController : public MessageReceiver { // Update patterns to the beat this->update_beat(); - // Detect manual overrides & update the current state to match. Segment& segment = strip.getMainSegment(); - if (segment.palette != this->current_state.palette_id) { - Serial.printf("Palette override = %d\n",segment.palette); - this->next_state.palette_phrase = 0; - this->next_state.palette_id = segment.palette; - this->broadcast_state(); + + // Detect manual overrides & update the current state to match. + uint8_t expected_palette = this->current_state.palette_id; + if (this->paletteOverride && !this->paletteOverrideTimer.ended()) { + expected_palette = this->paletteOverride; + } + if (segment.palette != expected_palette) { + if (segment.palette == 0) { + if (this->paletteOverride) { + // Selecting Default = allow Tubes to control palette. + Serial.println("Turning off WLED control of palette."); + this->paletteOverride = 0; + this->paletteOverrideTimer.stop(); + VirtualStrip::set_wled_palette(this->current_state.palette_id); + } + } else { + Serial.println("WLED has control of palette."); + this->paletteOverride = segment.palette; + this->paletteOverrideTimer.start(300000); // 5 minutes of manual control + } + } + + uint8_t wled_pattern_id = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (wled_pattern_id < 10) { + wled_pattern_id = DEFAULT_WLED_FX; } - /* TODO: detect WLED manual overrides - bool options_changed = false; - if (segment.speed != this->options.speed) { - Serial.printf("WLED FX speed: %d\n",segment.speed); - this->options.speed = segment.speed; - options_changed = true; + uint8_t expected_pattern = wled_pattern_id; + if (this->patternOverride && !this->patternOverrideTimer.ended()) { + expected_pattern = this->patternOverride; } - if (segment.intensity != this->options.intensity) { - Serial.printf("WLED FX intensity: %d\n",segment.intensity); - this->options.intensity = segment.intensity; - options_changed = true; + if (segment.mode != expected_pattern) { + if (segment.mode == 0) { + if (this->patternOverride) { + // Selecting Default = allow Tubes to control patterns. + Serial.println("Turning off WLED control of patterns."); + this->patternOverrideTimer.stop(); + this->patternOverride = 0; + transitionDelay = 8000; // Back to long transitions + VirtualStrip::set_wled_pattern(wled_pattern_id, 128, 128); + } + } else { + Serial.println("WLED has control of patterns."); + this->patternOverride = segment.mode; + this->patternOverrideTimer.start(300000); // 5 minutes of manual control + + transitionDelay = 500; // Quick transitions + } } - if (options_changed) - this->broadcast_options(); - */ do_pattern_changes(); @@ -234,9 +270,9 @@ class PatternController : public MessageReceiver { if (!this->node->is_following() || random(0, 5) == 0) { this->send_update(); } + } this->auto_updater.update(); - } #ifdef USELCD if (this->lcd->active) { @@ -252,6 +288,11 @@ class PatternController : public MessageReceiver { } void handleOverlayDraw() { + // In manual mode WLED is always active + if (this->patternOverride) { + this->wled_fader = 0xFFFF; + } + // Crossfade between the custom pattern engine and WLED uint8_t fader = this->wled_fader >> 8; if (fader < 255) { @@ -282,7 +323,10 @@ class PatternController : public MessageReceiver { } // Draw effects layers over whatever WLED is doing. - this->effects->draw(&strip); + // But not in manual (WLED) mode + if (!this->patternOverride) { + this->effects->draw(&strip); + } CRGB c; switch (this->auto_updater.status) { @@ -444,7 +488,9 @@ class PatternController : public MessageReceiver { this->current_state.palette_id = palette_id; Serial.println("Change palette"); - VirtualStrip::set_wled_palette(palette_id); + if (!this->paletteOverride) { + VirtualStrip::set_wled_palette(palette_id); + } } // Choose the palette to display at the next palette cycle @@ -502,7 +548,12 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (this->patternOverride) { + // Don't update WLED + background.wled_fx_id = 0; + } else { + background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; + } background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; @@ -648,6 +699,9 @@ class PatternController : public MessageReceiver { case '@': this->setPowerSave(!this->options.power_save); break; + case '~': + ESP.restart(); + break; case '-': b = this->options.brightness; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 4bea94a1e8..a15998cd30 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -82,15 +82,23 @@ class DebugController { auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); - Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u at %d\n", + Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u", mode_name, seg.mode, palette_name, seg.palette, seg.speed, - seg.intensity, - this->controller->wled_fader + seg.intensity ); + if (this->controller->patternOverride) { + Serial.printf(" (PATTERN %d)", this->controller->patternOverride); + } else { + Serial.printf(" at %d", this->controller->wled_fader); + } + if (this->controller->paletteOverride) { + Serial.printf(" (PALETTE %d)", this->controller->paletteOverride); + } + Serial.println(); Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", this->controller->auto_updater.location.version, diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 81b5f01ed3..0fc0ba05c2 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -129,11 +129,14 @@ class LightNode { } void configure_ap() { +#ifdef DEFAULT_WIFI + strcpy(clientSSID, DEFAULT_WIFI); + strcpy(clientPass, DEFAULT_WIFI_PASSWORD); +#else // Don't connect to any networks. - // strcpy(clientSSID, "Fish Tank"); - // strcpy(clientPass, "Fish Tank"); strcpy(clientSSID, ""); strcpy(clientPass, ""); +#endif // Try to hide the access point unless this is the "root" node if (this->is_following()) { diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 1e89b94f38..a9cd57a3c5 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -297,6 +297,10 @@ Running Dual Twinklecat Twinkleup +AUDIOREACTIVE +Midnoise +GravCenter + MAYBE GOOD PATTERNS Fillnoise Gradient diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 85c2486640..bb17ff3b36 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -9,7 +9,7 @@ #define DEFAULT_FADE_SPEED 100 #define MAX_VIRTUAL_LEDS 150 -#define DEFAULT_WLED_FX FX_MODE_FLOW +#define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE class VirtualStrip; typedef void (*BackgroundFn)(VirtualStrip *strip); @@ -76,11 +76,11 @@ class VirtualStrip { this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; - if (this->isWled()) { - uint8_t wled_fx_id = background.wled_fx_id; - if (wled_fx_id < 10) - wled_fx_id = DEFAULT_WLED_FX; - set_wled_pattern(wled_fx_id, 128, 128); + // If this is the strip that's controlling WLED, update WLED's vars + if (background.wled_fx_id) { + set_wled_pattern(background.wled_fx_id, 128, 128); + } + if (background.palette_id) { set_wled_palette(background.palette_id); } } @@ -102,6 +102,10 @@ class VirtualStrip { } static void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { + // Never set it to one of the default patterns + if (pattern_id < 10) + pattern_id = DEFAULT_WLED_FX; + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; From b623d0af6dbd4edf091ef175492a1e784847f51f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 21 Aug 2022 01:56:09 -0700 Subject: [PATCH 159/263] Rewrite the transitions engine and fix manual overrides --- usermods/Tubes/Tubes.h | 1 + usermods/Tubes/controller.h | 172 +++++++++++++++++++++------------ usermods/Tubes/virtual_strip.h | 42 -------- 3 files changed, 110 insertions(+), 105 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index c7bb874763..c5bda68df5 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -43,6 +43,7 @@ class TubesUsermod : public Usermod { fadeTransition = true; // Fade palette transitions transitionDelay = 8000; // Fade them for a long time strip.setTargetFps(60); + strip.setCCT(100); // Start timing globalTimer.setup(); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 77090ef167..ace882fa83 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,6 +15,8 @@ const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; +#define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE + #define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 2 // 4 @@ -149,8 +151,8 @@ class PatternController : public MessageReceiver { this->next_state.pattern_phrase = 0; this->next_state.palette_phrase = 0; this->next_state.effect_phrase = 0; - VirtualStrip::set_wled_palette(0); // Default palette - VirtualStrip::set_wled_pattern(0, 128, 128); // Default pattern + this->set_wled_palette(0); // Default palette + this->set_wled_pattern(0, 128, 128); // Default pattern this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); @@ -197,6 +199,41 @@ class PatternController : public MessageReceiver { } } + void set_palette_override(uint8_t value) { + if (value == this->paletteOverride) + return; + + this->paletteOverride = value; + if (value) { + Serial.println("WLED has control of palette."); + this->paletteOverrideTimer.start(300000); // 5 minutes of manual control + } else { + Serial.println("Turning off WLED control of palette."); + this->paletteOverrideTimer.stop(); + this->set_wled_palette(this->current_state.palette_id); + } + } + + void set_pattern_override(uint8_t value, uint8_t auto_mode) { + if (value == DEFAULT_WLED_FX && !this->patternOverride) + return; + if (value == this->patternOverride) + return; + + this->patternOverride = value; + if (value) { + Serial.println("WLED has control of patterns."); + this->patternOverrideTimer.start(300000); // 5 minutes of manual control + transitionDelay = 500; // Short transitions + } else { + Serial.println("Turning off WLED control of patterns."); + this->patternOverrideTimer.stop(); + transitionDelay = 8000; // Back to long transitions + + this->set_wled_pattern(auto_mode, 128, 128); + } + } + void update() { this->read_keys(); @@ -210,52 +247,19 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); // Detect manual overrides & update the current state to match. - uint8_t expected_palette = this->current_state.palette_id; - if (this->paletteOverride && !this->paletteOverrideTimer.ended()) { - expected_palette = this->paletteOverride; - } - if (segment.palette != expected_palette) { - if (segment.palette == 0) { - if (this->paletteOverride) { - // Selecting Default = allow Tubes to control palette. - Serial.println("Turning off WLED control of palette."); - this->paletteOverride = 0; - this->paletteOverrideTimer.stop(); - VirtualStrip::set_wled_palette(this->current_state.palette_id); - } - } else { - Serial.println("WLED has control of palette."); - this->paletteOverride = segment.palette; - this->paletteOverrideTimer.start(300000); // 5 minutes of manual control - } + if (this->paletteOverride && this->paletteOverrideTimer.ended()) { + this->set_palette_override(0); + } else if (segment.palette != this->current_state.palette_id) { + this->set_palette_override(segment.palette); } - uint8_t wled_pattern_id = gPatterns[this->current_state.pattern_id].wled_fx_id; - if (wled_pattern_id < 10) { - wled_pattern_id = DEFAULT_WLED_FX; - } - - uint8_t expected_pattern = wled_pattern_id; - if (this->patternOverride && !this->patternOverrideTimer.ended()) { - expected_pattern = this->patternOverride; - } - if (segment.mode != expected_pattern) { - if (segment.mode == 0) { - if (this->patternOverride) { - // Selecting Default = allow Tubes to control patterns. - Serial.println("Turning off WLED control of patterns."); - this->patternOverrideTimer.stop(); - this->patternOverride = 0; - transitionDelay = 8000; // Back to long transitions - VirtualStrip::set_wled_pattern(wled_pattern_id, 128, 128); - } - } else { - Serial.println("WLED has control of patterns."); - this->patternOverride = segment.mode; - this->patternOverrideTimer.start(300000); // 5 minutes of manual control - - transitionDelay = 500; // Quick transitions - } + uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (wled_mode < 10) + wled_mode = DEFAULT_WLED_FX; + if (this->patternOverride && this->patternOverrideTimer.ended()) { + this->set_pattern_override(0, wled_mode); + } else if (segment.mode != wled_mode) { + this->set_pattern_override(segment.mode, wled_mode); } do_pattern_changes(); @@ -481,16 +485,8 @@ class PatternController : public MessageReceiver { return; this->current_state.palette_phrase = tube_state.palette_phrase; - this->_load_palette(tube_state.palette_id); - } - - void _load_palette(uint8_t palette_id) { - this->current_state.palette_id = palette_id; - - Serial.println("Change palette"); - if (!this->paletteOverride) { - VirtualStrip::set_wled_palette(palette_id); - } + this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; + set_wled_palette(this->current_state.palette_id); } // Choose the palette to display at the next palette cycle @@ -548,21 +544,66 @@ class PatternController : public MessageReceiver { void update_background() { Background background; background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - if (this->patternOverride) { - // Don't update WLED - background.wled_fx_id = 0; - } else { - background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; - } + background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; background.palette_id = this->current_state.palette_id; background.sync = (SyncMode)this->current_state.pattern_sync_id; - // re-use virtual strips to prevent heap fragmentation + // Use one of the virtual strips to render the patterns. + // A WLED-based pattern exists on the virtual strip, but causes + // it to do nothing since WLED merging happens in handleOverlayDraw. + // Reuse virtual strips to prevent heap fragmentation for (uint8_t i = 0; i < NUM_VSTRIPS; i++) { this->vstrips[i]->fadeOut(); } this->vstrips[this->next_vstrip]->load(background); this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; + + set_wled_pattern(background.wled_fx_id, 128, 128); + set_wled_palette(background.palette_id); + } + + void set_wled_palette(uint8_t palette_id) { + if (this->paletteOverride) + palette_id = this->paletteOverride; + + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (seg.palette == palette_id) continue; + if (!seg.isActive()) continue; + seg.startTransition(strip.getTransition()); + seg.palette = palette_id; + } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } + + void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { + if (this->patternOverride) + pattern_id = this->patternOverride; + else if (pattern_id == 0) + pattern_id = DEFAULT_WLED_FX; // Never set it to solid + + // When fading IN, make the pattern transition immediate if possible + bool fadeIn = this->wled_fader < 2000; + for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { + Segment& seg = strip.getSegment(i); + if (!seg.isActive()) continue; + if (seg.mode == pattern_id) continue; + if (fadeIn) { + seg.startTransition(0); + } else { + seg.startTransition(strip.getTransition()); + } + seg.speed = speed; + seg.intensity = intensity; + strip.setMode(i, pattern_id); + } + stateChanged = true; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } + + static void set_wled_brightness(uint8_t brightness) { + strip.setBrightness(brightness); } void setBrightness(uint8_t brightness) { @@ -702,6 +743,11 @@ class PatternController : public MessageReceiver { case '~': ESP.restart(); break; + case 'M': + // Cancel any overrides + this->paletteOverrideTimer.stop(); + this->patternOverrideTimer.stop(); + break; case '-': b = this->options.brightness; diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index bb17ff3b36..e227610de0 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -75,54 +75,12 @@ class VirtualStrip { this->fader = 0; this->fade_speed = fade_speed; this->brightness = DEF_BRIGHT; - - // If this is the strip that's controlling WLED, update WLED's vars - if (background.wled_fx_id) { - set_wled_pattern(background.wled_fx_id, 128, 128); - } - if (background.palette_id) { - set_wled_palette(background.palette_id); - } } bool isWled() { return this->background.wled_fx_id != 0; } - static void set_wled_palette(uint8_t palette_id) { - for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { - Segment& seg = strip.getSegment(i); - if (seg.palette == palette_id) continue; - if (!seg.isActive()) continue; - seg.startTransition(strip.getTransition()); - seg.palette = palette_id; - } - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); - } - - static void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { - // Never set it to one of the default patterns - if (pattern_id < 10) - pattern_id = DEFAULT_WLED_FX; - - for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { - Segment& seg = strip.getSegment(i); - if (!seg.isActive()) continue; - if (seg.mode == pattern_id) continue; - seg.speed = speed; - seg.intensity = intensity; - strip.setMode(i, pattern_id); - } - stateChanged = true; - stateUpdated(CALL_MODE_DIRECT_CHANGE); - } - - static void set_wled_brightness(uint8_t brightness) { - strip.setBrightness(brightness); - } - - void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) { if (this->fade == Dead) From 35ce404d7df455b66d9e7da8dfa785227688b4f9 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 21 Aug 2022 04:28:30 -0700 Subject: [PATCH 160/263] More effects --- usermods/Tubes/effects.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 4907bdfd4f..bdb59fe957 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -145,10 +145,13 @@ static const EffectDef gEffects[] = { {{Glitter, Draw, Eighth, 120}, {MediumDuration, Chill}}, {{Glitter, Invert, Eighth, 40}, {ShortDuration, Chill}}, {{Beatbox2, Black}, {MediumDuration, MediumEnergy}}, + {{Beatbox2, Black}, {MediumDuration, MediumEnergy}}, {{Beatbox2, Draw}, {ShortDuration, HighEnergy}}, {{Bubble, Darken}, {MediumDuration, Chill}}, {{Bubble, Brighten}, {MediumDuration, Chill}}, {{Bubble, Brighten}, {MediumDuration, Chill}}, + {{Bubble, Darken}, {MediumDuration, Chill}}, + {{Bubble, Invert}, {MediumDuration, Chill}}, {{Bubble, Invert}, {MediumDuration, Chill}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, MediumEnergy}}, {{Glitter, Flicker, Eighth, 120}, {MediumDuration, Chill}}, From 8a85504386de794bc685c52226771955b3be5075 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 22 Aug 2022 04:25:02 -0700 Subject: [PATCH 161/263] Clean up unused --- usermods/Tubes/TimeSync/counter.h | 409 ----------------------------- usermods/Tubes/TimeSync/sync.h | 180 ------------- usermods/Tubes/TimeSync/timesync.h | 397 ---------------------------- usermods/Tubes/bluetooth.h | 4 +- usermods/Tubes/firmware.sh | 69 +++++ 5 files changed, 71 insertions(+), 988 deletions(-) delete mode 100644 usermods/Tubes/TimeSync/counter.h delete mode 100644 usermods/Tubes/TimeSync/sync.h delete mode 100644 usermods/Tubes/TimeSync/timesync.h create mode 100755 usermods/Tubes/firmware.sh diff --git a/usermods/Tubes/TimeSync/counter.h b/usermods/Tubes/TimeSync/counter.h deleted file mode 100644 index a35187de1c..0000000000 --- a/usermods/Tubes/TimeSync/counter.h +++ /dev/null @@ -1,409 +0,0 @@ -/** \file - \brief Counter Math - \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of Counter nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -/** \page Counter Math - - Represents an unsigned counter that can roll over from its maximum value - back to zero. A common example is a 32-bit timestamp from GetTickCount() - on Windows, which can roll-over and cause software bugs despite testing. - This also provides compression for counters. - - This class provides: - + Counters of 2 bits through 64 bits e.g. 24-bit counters - + Increment/decrement by 1 or a constant - + Safe comparison operator overloads - + Compression/decompression via truncation - + Unit tested software -*/ - -#include -#include - -// Compiler-specific force inline keyword -#if defined(_MSC_VER) - #define COUNTER_FORCE_INLINE inline __forceinline -#else // _MSC_VER - #define COUNTER_FORCE_INLINE inline __attribute__((always_inline)) -#endif // _MSC_VER - - -//------------------------------------------------------------------------------ -// Counter - -template class Counter -{ -public: - typedef Counter ThisType; - typedef T ValueType; - typedef typename std::make_signed::type SignedType; - - static const unsigned kBits = TkBits; ///< Number of data bits - static const T kMSB = (T)1 << (TkBits - 1); ///< Most Significant Bit - - /// Generate a bit mask with 1s for each data bit - /// The mask gets optimized away when compiler optimizations are enabled - static const T kMask = static_cast(-(int64_t)1) >> (sizeof(T) * 8 - kBits); - - /// Counter value - T Value; - - - //-------------------------------------------------------------------------- - // Assignment - - Counter(T value = 0) - : Value(value & kMask) - { - } - Counter(const ThisType& b) - : Value(b.Value) - { - } - - ThisType& operator=(T value) - { - Value = value & kMask; - return *this; - } - ThisType& operator=(const ThisType b) - { - Value = b.Value; - return *this; - } - - - //-------------------------------------------------------------------------- - // Accessors - - /// Get current value - COUNTER_FORCE_INLINE T ToUnsigned() const - { - return Value; - } - - - //-------------------------------------------------------------------------- - // Increment - - /// Pre-increment - COUNTER_FORCE_INLINE ThisType& operator++() - { - Value = (Value + 1) & kMask; - return *this; - } - - /// Pre-decrement - COUNTER_FORCE_INLINE ThisType& operator--() - { - Value = (Value - 1) & kMask; - return *this; - } - - /// Post-increment - COUNTER_FORCE_INLINE ThisType operator++(int) - { - const T oldValue = Value; - Value = (Value + 1) & kMask; - return oldValue; - } - - /// Post-decrement - COUNTER_FORCE_INLINE ThisType operator--(int) - { - const T oldValue = Value; - Value = (Value - 1) & kMask; - return oldValue; - } - - - //-------------------------------------------------------------------------- - // Addition - - COUNTER_FORCE_INLINE ThisType& operator+=(const ThisType b) - { - Value = (Value + b.Value) & kMask; - return *this; - } - - COUNTER_FORCE_INLINE ThisType operator+(const ThisType b) const - { - return Value + b.Value; // Implicit mask - } - - COUNTER_FORCE_INLINE ThisType& operator-=(const ThisType b) - { - Value = (Value - b.Value) & kMask; - return *this; - } - - COUNTER_FORCE_INLINE ThisType operator-(const ThisType b) const - { - return Value - b.Value; // Implicit mask - } - - - //-------------------------------------------------------------------------- - // Comparison - // We can only compare counters of the same type - - COUNTER_FORCE_INLINE bool operator==(const ThisType b) const - { - return Value == b.Value; - } - COUNTER_FORCE_INLINE bool operator!=(const ThisType b) const - { - return Value != b.Value; - } - COUNTER_FORCE_INLINE bool operator>=(const ThisType b) const - { - const T d = static_cast(Value - b.Value) & kMask; - return d < kMSB; - } - COUNTER_FORCE_INLINE bool operator<(const ThisType b) const - { - const T d = static_cast(Value - b.Value) & kMask; - return d >= kMSB; - } - COUNTER_FORCE_INLINE bool operator<=(const ThisType b) const - { - const T d = static_cast(b.Value - Value) & kMask; - return d < kMSB; - } - COUNTER_FORCE_INLINE bool operator>(const ThisType b) const - { - const T d = static_cast(b.Value - Value) & kMask; - return d >= kMSB; - } - - - //-------------------------------------------------------------------------- - // Counter Compression (Truncation) and Re-Expansion - - /** - These routines will truncate a counter to a smaller number of bits, - and later expand the smaller value back into the original counter value - provided with a reference counter. For example a 64-bit timestamp can - be compressed down to 24 bits, sent over a network, and then expanded - back to the original value given a local time at the receiver. - - This assumes that counters are counting up and that roll-over can only - happen one time. If a counter rolls over twice, then the resulting - expanded counter value will be incorrect. - */ - - /// Compress to smaller counter by truncating - template - COUNTER_FORCE_INLINE SmallerT Truncate() const - { - static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); - - // Truncate to smaller type - return SmallerT(static_cast(Value)); - } - - /// Expand from truncated counter - /// Bias > 0 can be used to accept values farther in the past - /// Bias < 0 can be used to accept values farther in the future - template - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncatedWithBias( - const ThisType recent, - const SmallerT smaller, - const SignedType bias) - { - static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); - - /** - The bits in the smaller counter were all truncated from the correct - value, so what needs to be determined now is all the higher bits. - - Examples: - - Recent Smaller => Expanded - ------ ------- -------- - 0x100 0xff 0x0ff - 0x16f 0x7f 0x17f - 0x17f 0x6f 0x16f - 0x1ff 0xa0 0x1a0 - 0x1ff 0x01 0x201 - - The choice to make is between -1, 0, +1 for the next bit position. - - Since we have no information about the high bits, it should be - sufficient to compare the recent low bits with the smaller value - in order to decide which one is correct: - - 00 - ff = -ff -> -1 - 6f - 7f = -10 -> 0 - 7f - 6f = +10 -> 0 - ff - a0 = +5f -> 0 - ff - 01 = +fe -> +1 - */ - - // First insert the low bits to get the default result - ThisType result = smaller.ToUnsigned() | (recent.ToUnsigned() & ~static_cast(SmallerT::kMask)); - - // Grab the low bits of the recent counter - const T recentLow = recent.ToUnsigned() & SmallerT::kMask; - - // If recent - smaller would be negative: - if (recentLow < smaller.ToUnsigned()) - { - // If it is large enough to roll back a MSB: - const T absDiff = smaller.ToUnsigned() - recentLow; - if (absDiff >= static_cast(SmallerT::kMSB - bias)) - result -= static_cast(SmallerT::kMSB) << 1; - } - else - { - // If it is large enough to roll ahead a MSB: - const T absDiff = recentLow - smaller.ToUnsigned(); - if (absDiff > static_cast(SmallerT::kMSB + bias)) - result += static_cast(SmallerT::kMSB) << 1; - } - - return result; - } - - /// Expand from truncated counter without any bias - template - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const SmallerT smaller) - { - static_assert(SmallerT::kBits < kBits, "Smaller type must be smaller"); - - ValueType smallerMSB = smaller.Value & SmallerT::kMSB; - SignedType smallerSigned = smaller.Value - (smallerMSB << 1); - - auto smallRecent = static_cast(recent.Value & SmallerT::kMask); - - // Signed gap = partial - prev - auto gap = static_cast(smallerSigned - smallRecent) & SmallerT::kMask; - - ValueType gapMSB = gap & SmallerT::kMSB; - SignedType gapSigned = gap - (gapMSB << 1); - - // Result = recent + gap - return recent.Value + gapSigned; - } - - // Template specialization to optimize cases where the word size matches - // the field size. Otherwise the extra sign handling above is not elided - // by the compiler's optimizer: - - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const Counter smaller) - { - static_assert(32 < kBits, "Smaller type must be smaller"); - - const int32_t gap = static_cast(smaller.Value - static_cast(recent.Value)); - return recent + gap; - } - - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const Counter smaller) - { - static_assert(16 < kBits, "Smaller type must be smaller"); - - const int16_t gap = static_cast( smaller.Value - static_cast(recent.Value) ); - return recent + gap; - } - - static COUNTER_FORCE_INLINE ThisType ExpandFromTruncated( - const ThisType recent, - const Counter smaller) - { - static_assert(8 < kBits, "Smaller type must be smaller"); - - const int8_t gap = static_cast(smaller.Value - static_cast(recent.Value)); - return recent + gap; - } - - - static_assert(std::is_pod::value, "Type must be a plain-old data type"); - static_assert(std::is_unsigned::value, "Type must be unsigned"); - static_assert(TkBits > 0, "Invalid input"); - static_assert(sizeof(T) * 8 >= TkBits, "Base type is not wide enough"); - - static_assert(kMask != 0, "bugcheck"); - static_assert(kMSB < kMask, "bugcheck"); - static_assert(kMSB != 0, "bugcheck"); -}; - - -/// Convenience declarations -typedef Counter Counter64; -typedef Counter Counter56; -typedef Counter Counter48; -typedef Counter Counter40; -typedef Counter Counter32; -typedef Counter Counter24; -typedef Counter Counter16; -typedef Counter Counter10; -typedef Counter Counter8; -typedef Counter Counter4; - -static_assert(sizeof(Counter8) == 1, "Unexpected padding"); -static_assert(sizeof(Counter16) == 2, "Unexpected padding"); -static_assert(sizeof(Counter32) == 4, "Unexpected padding"); -static_assert(sizeof(Counter64) == 8, "Unexpected padding"); - -/** - CounterExpand() - - This is a common utility function that expands a 1-7 byte truncated - counter back into a 64-bit (8 byte) counter, based on the largest - counter value seen so far. - - Preconditions: bytes > 0 && bytes < 8 -*/ -COUNTER_FORCE_INLINE Counter64 CounterExpand( - uint64_t largest, - uint64_t partial, - unsigned bytes) -{ - switch (bytes) - { - case 1: return Counter64::ExpandFromTruncated(largest, Counter8((uint8_t)partial)); - case 2: return Counter64::ExpandFromTruncated(largest, Counter16((uint16_t)partial)); - case 3: return Counter64::ExpandFromTruncated(largest, Counter24((uint32_t)partial)); - case 4: return Counter64::ExpandFromTruncated(largest, Counter32((uint32_t)partial)); - case 5: return Counter64::ExpandFromTruncated(largest, Counter40(partial)); - case 6: return Counter64::ExpandFromTruncated(largest, Counter48(partial)); - case 7: return Counter64::ExpandFromTruncated(largest, Counter56(partial)); - default: - break; - } - - return 0; -} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/sync.h b/usermods/Tubes/TimeSync/sync.h deleted file mode 100644 index da8cb7234f..0000000000 --- a/usermods/Tubes/TimeSync/sync.h +++ /dev/null @@ -1,180 +0,0 @@ -/** \file - \brief TimeSync: Time Synchronization - \copyright Copyright (c) 2017-2019 Christopher A. Taylor. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of TimeSync nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "timesync.h" - - -//------------------------------------------------------------------------------ -// WindowedMinTS24 - -void WindowedMinTS24::Update( - Counter24 value, - uint64_t timestamp, - const uint64_t windowLengthTime) -{ - const Sample sample(value, timestamp); - - // On the first sample, new best sample, or if window length has expired: - if (!IsValid() || - value <= Samples[0].Value || - Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime)) - { - Reset(sample); - return; - } - - // Insert the new value into the sorted array - if (value <= Samples[1].Value) - Samples[2] = Samples[1] = sample; - else if (value <= Samples[2].Value) - Samples[2] = sample; - - // Expire best if it has been the best for a long time - if (Samples[0].TimeoutExpired(sample.Timestamp, windowLengthTime)) - { - // Also expire the next best if needed - if (Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime)) - { - Samples[0] = Samples[2]; - Samples[1] = sample; - } - else - { - Samples[0] = Samples[1]; - Samples[1] = Samples[2]; - } - Samples[2] = sample; - return; - } - - // Quarter of window has gone by without a better value - Use the second-best - if (Samples[1].Value == Samples[0].Value && - Samples[1].TimeoutExpired(sample.Timestamp, windowLengthTime / 4)) - { - Samples[2] = Samples[1] = sample; - return; - } - - // Half the window has gone by without a better value - Use the third-best one - if (Samples[2].Value == Samples[1].Value && - Samples[2].TimeoutExpired(sample.Timestamp, windowLengthTime / 2)) - { - Samples[2] = sample; - } -} - - -//------------------------------------------------------------------------------ -// TimeSynchronizer - -void TimeSynchronizer::OnPeerMinDeltaTS24(Counter24 minDeltaTS24) -{ - LastFC_MinDeltaTS24 = minDeltaTS24; - GotPeerUpdate = true; - - Recalculate(); -} - -unsigned TimeSynchronizer::OnAuthenticatedDatagramTimestamp( - Counter24 remoteSendTS24, - uint64_t localRecvUsec) -{ - const Counter24 localTS24 = (uint32_t)(localRecvUsec >> kTime23LostBits); - - // OWD_i + ClockDelta(L-R)_i = Local Receive Time - Remote Send Time - const Counter24 deltaTS24 = localTS24 - remoteSendTS24; - - WindowedMinTS24Deltas.Update(deltaTS24, localRecvUsec, kDriftWindowUsec); - - Recalculate(); - - // Estimated one-way-delay (OWD) for this datagram in microseconds. - // This does not include processing time only network delay and perhaps - // some delays from the Operating System when it is heavily loaded. - // Set to 0 if trip time is not available - unsigned networkTripUsec = 0; - - if (IsSynchronized()) - { - // This is equivalent to the shortest RTT/2 seen so far by any pair of packets, - // meaning that it is the average of the upstream and downstream OWD. - networkTripUsec = GetMinimumOneWayDelayUsec(); - - // While the OWD is an estimate, the relative delay between that - // smallest packet pair and the current datagram is actually precise: - const Counter24 minDeltaTS24 = GetMinDeltaTS24(); - if (deltaTS24 > minDeltaTS24) - { - const Counter24 relativeTS24 = deltaTS24 - minDeltaTS24; - networkTripUsec += relativeTS24.ToUnsigned() << kTime23LostBits; - } - - // What should happen here is if the delay of each packet varies a lot, then we should - // get pretty accurate OWD for each packet. But if the variance is low and the delays - // for upstream and downstream are asymmetric, then it will underestimate the OWD by - // half of that asymmetry. Hopefully this inaccuracy won't cause problems.. - } - - return networkTripUsec; -} - -void TimeSynchronizer::Recalculate() -{ - if (!WindowedMinTS24Deltas.IsValid() || !GotPeerUpdate) - return; - - // min(OWD_i) + ClockDelta(L-R)_i - const Counter24 minRecvDeltaTS24 = WindowedMinTS24Deltas.GetBest(); - - // min(OWD_j) + ClockDelta(R-L)_j - const Counter24 minSendDeltaTS24 = LastFC_MinDeltaTS24; - - // Assume min(OWD_i) = min(OWD_j): - // min(OWD) ~= (min(OWD_j) + min(OWD_i)) / 2 - const Counter23 minOWD_TS23 = (minSendDeltaTS24 + minRecvDeltaTS24).ToUnsigned() >> 1; - - // Assume ClockDelta(R-L)_j = -ClockDelta(L-R)_i: - // ClockDelta(R-L) ~= (ClockDelta(R-L)_j - ClockDelta(L-R)_i) / 2 - const Counter23 clockDelta_TS23 = (minSendDeltaTS24 - minRecvDeltaTS24).ToUnsigned() >> 1; - - // Calculate the time delta in microseconds - RemoteTimeDeltaUsec = clockDelta_TS23.ToUnsigned() << kTime23LostBits; - - // Calculate the minimum OWD, which may go negative and blow up.. - uint32_t min_owd_usec = minOWD_TS23.ToUnsigned() << kTime23LostBits; - - // If the implied subtraction went negative, correct to zero: - static const uint32_t kSignRolloverThreshold = (1 << 22) << kTime23LostBits; - if (min_owd_usec >= kSignRolloverThreshold) { - min_owd_usec = 0; - } - MinimumOneWayDelayUsec = min_owd_usec; - - Synchronized = true; -} \ No newline at end of file diff --git a/usermods/Tubes/TimeSync/timesync.h b/usermods/Tubes/TimeSync/timesync.h deleted file mode 100644 index 48a04f5b31..0000000000 --- a/usermods/Tubes/TimeSync/timesync.h +++ /dev/null @@ -1,397 +0,0 @@ -/** \file - \brief TimeSync: Time Synchronization - \copyright Copyright (c) 2017-2018 Christopher A. Taylor. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of TimeSync nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "counter.h" - -#include - -/** - Time Synchronization Protocol - - -- Motivation: - - Time synchronization is an important core component of an rUDP library, - enabling multiple advantages over reliable UDP libraries without: - - (1) This specific (new) time synchronization works better over cellular - networks than PTP/NTP. - - (2) The API provides time synchronization as a feature for applications, - enabling millisecond-accurate dead reckoning for video games, and - 16-microsecond-accurate timing for scientific applications with 2-3 bytes. - - (3) Peer2Peer NAT hole-punch can be optimized because it can use time - synchronization to initiate probes simultaneously on both peers. - - (4) Delay-based Congestion Control systems should use One Way Delay (OWD) - on each packet as a signal, which allows it to e.g. avoid causing latency - in realtime games while delivering a file transfer in the background. - All existing Delay-based CC algorithms use differential OWD rather than - proper time synchronization. By adding time synchronization, CC becomes - robust to changes in the base OWD as the end-points remain synced. - - -- Background: - - Network time synchronization can be done two ways: - (a) Broadcast - Infeasible on the Internet and so not used. - (b) Assuming that the link is symmetric, and trusting Min(RTT/2) = OWD. - Meaning that existing network time synchronization protocols like NTP and - PTP work by sending multiple probes, and then taking the probe with the - smallest round trip time to be the best data to use in the set of probes. - - Time synchronization at higher resolutions needs to be performed constantly - because clocks drift at a rate of about 1 millisecond per 10 seconds. - So, a common choice for reliable UDP game protocols is to probe for time - synchronization purposes (running NTP all the time) at a fixed interval of - e.g. 5 to 10 seconds. - - The disadvantage of all of these existing approaches is that they only use - a finite number of probes, and all probes may be slightly skewed, - especially when jitter is present, or cross-traffic, or self-congestion - from a file transfer. - - For cellular networks, existing time synchronization approaches provide - degraded results due to the 4-10 millisecond jitter on every packet. - "An End-to-End Measurement Study of Modern Cellular Data Networks" (2014) - https://www.comp.nus.edu.sg/~bleong/publications/pam14-ispcheck.pdf - - when a link is asymmetrical there is no known practical method for - performing time synchronization between two peers that meet on the Internet, - so in that case a best effort is done, and at least it will be consistent. - - -- Algorithm: - - This TimeSync protocol overcomes this jitter problem by using a massive - number of probes (every packet is a probe), greatly increasing the odds - of a minimal RTT probe. - - How it works is that every packet has a 3 byte microsecond timestamp on it, - large enough to prevent roll-over. Both sides record the receive time of - each packet as early as possible, and then throw the packet onto another - thread to process, so that the network delay is more accurate and each - client of a server can run in parallel. - - While each packet is being processed the difference in send and receive - times are compared with prior such differences. And a windowed minimum - of these differences is updated. Periodically this minimum difference - is reported to the remote peer so that both sides have both the minimum - (outgoing - incoming) difference and the minimum (incoming - outgoing) - difference. - - These minimum differences correspond to the shortest trips each way. - Effectively, it turns every single packet into a time synchronization - probe, guaranteeing that it gets the best result possible. - - -- The (Simple) Math for TimeSync: - - We measure (Smallest C2S Delta) and (Smallest S2C Delta) - through the per-packet timestamps. - - C2S = (Smallest C2S Delta) - = (Server Time @ Client Send Time) + (C2S Propagation Delay) - (Client Send Time) - = (C2S Propagation Delay) + (Clock Delta) - - S2C = (Smallest S2C Delta) - = (Client Time @ Server Send Time) + (S2C Propagation Delay) - (Server Send Time) - = (S2C Propagation Delay) - (Clock Delta) - - Such that (Propagation Delay) for each direction is minimized - independently as described previously. - - We want to solve for (Clock Delta) but there is a problem: - - Note that in the definition of C2S and S2C there are three unknowns and - only two measurements. To resolve this problem we make the assumption - that the min. propagation delays are almost the same in each direction. - - And so: - - (S2C Propagation Delay) approx equals (C2S Propagation Delay). - - Thus we can simply write: - - (Clock Delta) = (C2S - S2C) / 2 - - This gives us 23 bits of the delta between clocks, since the division - by 2 (right shift) pulls in an unknown high bit. The effect of any - link asymmetry is halved as a side effect, helping to minimize it. - - -- The (Simple) Math for Network Trip Time: - - It also provides a robust estimate of the "speed of light" between two - network hosts (minimal one-way delay). This information is then used to - calculate the network trip time for every packet that arrives: - - Let DD = Distance from the current packet timestamp difference and minimal. - DD = (Packet Receive - Packet Send) - Min(Packet Receive - Packet Send) - Packet trip time = (Minimal one-way delay) + DD. -*/ - - -//------------------------------------------------------------------------------ -// Constants - -/// Default One Way Delay (OWD) to return before time sync completes -static const uint32_t kDefaultOWDUsec = 200 * 1000; ///< 200 ms - -/// Number of bits removed from the low end of the microsecond timestamp -static const unsigned kTime23LostBits = 3; - -/// Largest 23-bit counter difference value considered positive -static const unsigned kTime23Bias = 0x200000; - -/// Error bound for 23-bit timestamps <= 8*2-1 = 15 microseconds -static const unsigned kTime23ErrorBound = (1 << kTime23LostBits) * 2 - 1; - -/// Number of bits removed from the low end of the microsecond timestamp -static const unsigned kTime16LostBits = 9; - -/// Largest 16-bit counter difference value considered positive -static const unsigned kTime16Bias = 0x4000; - -/// Error bound for 16-bit timestamps <= 512*2-1 = 1.023 milliseconds -static const unsigned kTime16ErrorBound = (1 << kTime16LostBits) * 2 - 1; - -/// Window size for WindowedMinTS24Deltas. -/// Since clocks drift over time, eventually old measurements must be ignored. -/// This is also the longest that a timing measurement will affect time synch. -/// Assumes that clocks drift 1 millisecond every 10 seconds -static const uint64_t kDriftWindowUsec = 10 * 1000 * 1000; ///< 10 seconds - - -//------------------------------------------------------------------------------ -// Types - -/// Use Counter23::Decompress to expand back to 64-bit counters -typedef Counter Counter23; - - -//------------------------------------------------------------------------------ -// WindowedMinTS24 - -/// Windowed minimum in TS24 units -class WindowedMinTS24 -{ -public: - WindowedMinTS24() {} - - struct Sample - { - /// Sample value - Counter24 Value; - - /// Timestamp of data collection - uint64_t Timestamp; - - - /// Default values and initializing constructor - explicit Sample(Counter24 value = 0, uint64_t timestamp = 0) - : Value(value) - , Timestamp(timestamp) - { - } - - /// Check if a timeout expired - inline bool TimeoutExpired(uint64_t now, uint64_t timeout) - { - return (uint64_t)(now - Timestamp) > timeout; - } - }; - - - /// Number of samples collected - static const unsigned kSampleCount = 3; - - /// Sorted samples from smallest to largest - Sample Samples[kSampleCount]; - - - /// Are there any samples? - inline bool IsValid() const - { - return Samples[0].Value != 0; ///< ish - } - - /// Get smallest sample - inline Counter24 GetBest() const - { - return Samples[0].Value; - } - - /// Reset samples - inline void Reset(const Sample sample = Sample()) - { - Samples[0] = Samples[1] = Samples[2] = sample; - } - - /// Update minimum with new value - void Update( - Counter24 value, - uint64_t timestamp, - const uint64_t windowLengthTime); -}; - - -//------------------------------------------------------------------------------ -// TimeSynchronizer - -class TimeSynchronizer -{ -public: - /** - OnPeerMinDeltaTS24() - - Call this when the peer provides its latest 24-bit MinDeltaTS24 value. - The peer should do this periodically, ideally faster in the first minute - and then settling down to once every 2 seconds or so. This can be used - as a keep-alive for example. - - minDeltaTS24: Provide the low 24 bits of the peer's delta value. - */ - void OnPeerMinDeltaTS24(Counter24 minDeltaTS24); - - /// Convert local time in microseconds to a 24-bit datagram timestamp - static inline uint32_t LocalTimeToDatagramTS24(uint64_t localUsec) - { - return (uint32_t)(localUsec >> kTime23LostBits) & 0x00ffffff; - } - - /** - OnAuthenticatedDatagramTimestamp() - - Call this when a datagram arrives with an attached 24-bit timestamp. - Ideally every UDP/IP datagram we receive will have a timestamp. - It is recommended to check if the datagram is from the peer before - accepting the timestamp on the datagram. - - remoteSendTS24: The 24-bit timestamp attached to an incoming datagram. - localRecvUsec: A recent timestamp in microsecond units. - - Returns estimated one way delay (OWD) in microseconds for this datagram. - Returns 0 if OWD is unavailable. - */ - unsigned OnAuthenticatedDatagramTimestamp( - Counter24 remoteSendTS24, - uint64_t localRecvUsec); - - - /// Get the minimum TS24 (receipt - send) delta seen in the past interval - inline Counter24 GetMinDeltaTS24() const - { - return WindowedMinTS24Deltas.GetBest(); - } - - /// Is time synchronized? - inline bool IsSynchronized() const - { - return Synchronized; - } - - /// Get the minimum one-way delay seen so far. - /// This is equivalent to the shortest RTT/2 seen so far by any pair of packets, - /// meaning that it is the average of the upstream and downstream OWD. - inline uint32_t GetMinimumOneWayDelayUsec() const - { - return MinimumOneWayDelayUsec; - } - - /// Returns 16-bit remote time field to send in a packet - inline uint16_t ToRemoteTime16(uint64_t localUsec) - { - if (!Synchronized) { - return 0; - } - - const uint16_t localTS16 = (uint16_t)(localUsec >> kTime16LostBits); - const uint16_t deltaTS16 = (uint16_t)(RemoteTimeDeltaUsec >> kTime16LostBits); - - return localTS16 + deltaTS16; - } - - /// Returns local time given local time from packet - inline uint64_t FromLocalTime16( - uint64_t localUsec, - Counter16 timestamp16) - { - return Counter64::ExpandFromTruncatedWithBias( - localUsec >> kTime16LostBits, - timestamp16, - kTime16Bias).ToUnsigned() << kTime16LostBits; - } - - /// Returns 23-bit remote time field to send in a packet - inline uint32_t ToRemoteTime23(uint64_t localUsec) - { - if (!Synchronized) { - return 0; - } - - const Counter23 localTS23 = (uint32_t)(localUsec >> kTime23LostBits); - const Counter23 deltaTS23 = RemoteTimeDeltaUsec >> kTime23LostBits; - - return (localTS23 + deltaTS23).ToUnsigned(); - } - - /// Returns local time given remote time from packet - inline uint64_t FromLocalTime23( - uint64_t localUsec, - Counter23 timestamp23) - { - return Counter64::ExpandFromTruncatedWithBias( - localUsec >> kTime23LostBits, - timestamp23, - kTime23Bias).ToUnsigned() << kTime23LostBits; - } - -protected: - /// Synchronized? - std::atomic Synchronized = ATOMIC_VAR_INIT(false); - - /// Calculated delta = (Remote time - Local time) - std::atomic RemoteTimeDeltaUsec = ATOMIC_VAR_INIT(0); ///< usec - - /// Calculated minimum OWD - std::atomic MinimumOneWayDelayUsec = ATOMIC_VAR_INIT(kDefaultOWDUsec); ///< in usec - - /// Windowed minimum value for received packet timestamp deltas - /// Keep track of the smallest (receipt - send) time delta seen so far - WindowedMinTS24 WindowedMinTS24Deltas; ///< in Timestamp24 units - - /// Keep a copy of the last MinDeltaUsec from the flow control data from peer - Counter24 LastFC_MinDeltaTS24 = 0; - - /// Is peer update received yet? - bool GotPeerUpdate = false; - - - /// Recalculate MinimumOneWayDelayUsec and RemoteTimeDeltaUsec - void Recalculate(); -}; \ No newline at end of file diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 4c11d89872..0322264939 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -1,5 +1,7 @@ #pragma once +// THIS FILE ISN'T USED ANY MORE + #include #include #include "global_state.h" @@ -7,8 +9,6 @@ #include "node.h" -// #include "TimeSync/sync.h" - #define MAX_CONNECTED_CLIENTS 3 #define DATA_UPDATE_SERVICE "D00B" diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh new file mode 100755 index 0000000000..5c983acc4a --- /dev/null +++ b/usermods/Tubes/firmware.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Updates new boards (which start as broadcasting on WLED-AP) to custom firmware +# Will update as many boards as are plugged in, one at a time. + +WLEDPATH=../../build_output/firmware +ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools + +update_firmware() { + echo "Updating firmware via OTA" + curl -s -F "update=@../../build_output/firmware/esp32_quinled_uno.bin" $1/update >/dev/null + echo "Updated; wait..." + sleep 5 +} + +update_config() { + curl -s http://$1/upload -F "data=@cfg-tmp.json;filename=/cfg.json" >/dev/null + curl -s http://$1/reset >/dev/null +} + +connect() { + if ! networksetup -getairportnetwork en0 | grep $1 + then + echo "Connecting to $1" + networksetup -setairportnetwork en0 $1 "wled1234" + echo "Connected; wait..." + sleep 5 + fi +} + +start() { + connect "WLED-AP" + + ping -c 1 -t 2 $1 >/dev/null + PINGRESULT=$? + if [ $PINGRESULT -eq 0 ]; then + update_firmware $1 + else + echo Missing $1 + fi + return 1 +} + +# TODO: extract the config-creation from here +update_one() { + ping -c 1 -t 2 $1 >/dev/null + PINGRESULT=$? + if [ $PINGRESULT -eq 0 ]; then + echo Updating $1 + curl -s http://$1/presets.json -o presets-tmp.json >/dev/null + curl -s http://$1/cfg.json -o cfg-tmp.json >/dev/null + + update_firmware $1 + curl -s -F "data=@cfg-tmp.json;filename=/cfg.json" http://$1/upload >/dev/null + curl -s -F "data=@presets-tmp.json;filename=/presets.json" http://$1/upload >/dev/null + curl -s http://$1/reset >/dev/null + rm presets-tmp.json + rm cfg-tmp.json + return 0 + else + echo Missing $1 + fi + return 1 +} + +while : +do + start 4.3.2.1 +done From 7342d3d88cc62d1457a8b7daceb83cc4eb315af5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 22 Aug 2022 18:47:35 -0700 Subject: [PATCH 162/263] Fix OTA updates --- usermods/Tubes/controller.h | 14 ++++-- usermods/Tubes/default_config.json | 1 + usermods/Tubes/firmware.sh | 70 +++++++++++++++++------------- usermods/Tubes/node.h | 10 +---- usermods/Tubes/update_server.py | 2 +- usermods/Tubes/updater.h | 44 ++++++++++++++----- wled00/button.cpp | 6 ++- 7 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 usermods/Tubes/default_config.json diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ace882fa83..f7a37be542 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -332,6 +332,16 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } + drawOTAOverlay(); + + // When AP mode is on, make sure it's obvious + if (apActive) { + strip.setPixelColor(0, CRGB::Purple); + strip.setPixelColor(1, CRGB::Black); + } + } + + void drawOTAOverlay() { CRGB c; switch (this->auto_updater.status) { case Started: @@ -358,7 +368,6 @@ class PatternController : public MessageReceiver { for (int i = 0; i < 20; i++) { strip.setPixelColor(i, c); } - } void restart_phrase() { @@ -931,8 +940,7 @@ class PatternController : public MessageReceiver { } case COMMAND_UPGRADE: - memcpy((byte*)&this->auto_updater.location, (byte*)data, sizeof(AutoUpdateOffer)); - this->auto_updater.start(); + this->auto_updater.start((AutoUpdateOffer*)data); return; } diff --git a/usermods/Tubes/default_config.json b/usermods/Tubes/default_config.json new file mode 100644 index 0000000000..55c340b5f7 --- /dev/null +++ b/usermods/Tubes/default_config.json @@ -0,0 +1 @@ +{"rev":[1,0],"vid":2208180,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"Tube","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":1000,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0}]},"com":[],"btn":{"max":4,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8},"tr":{"mode":true,"dur":80,"pal":1},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"twice":false,"grp":1}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"addr":1,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0]},"hue":{"en":false,"id":1,"iv":25,"recv":{"on":true,"bri":true,"col":true},"ip":[0,0,0,0]},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":false,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1,-1,-1]},"cfg":{"squelch":10,"gain":60,"AGC":0},"dynamics":{"Limiter":true,"Rise":80,"Fall":1400},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 5c983acc4a..38e7d26d4a 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -6,30 +6,41 @@ WLEDPATH=../../build_output/firmware ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools +upload_firmware() { + echo "Uploading firmware" + sftp control@brcac.com </dev/null + curl -s http://$1/reset >/dev/null +} + update_firmware() { echo "Updating firmware via OTA" curl -s -F "update=@../../build_output/firmware/esp32_quinled_uno.bin" $1/update >/dev/null echo "Updated; wait..." sleep 5 -} - -update_config() { - curl -s http://$1/upload -F "data=@cfg-tmp.json;filename=/cfg.json" >/dev/null - curl -s http://$1/reset >/dev/null + echo "Updating configuration via OTA" + update_config $1 } connect() { - if ! networksetup -getairportnetwork en0 | grep $1 + if ! networksetup -getairportnetwork en0 | grep "$1" then echo "Connecting to $1" - networksetup -setairportnetwork en0 $1 "wled1234" + networksetup -setairportnetwork en0 "$1" "$2" echo "Connected; wait..." sleep 5 fi } -start() { - connect "WLED-AP" +update_one() { + connect "$2" "$3" ping -c 1 -t 2 $1 >/dev/null PINGRESULT=$? @@ -41,29 +52,28 @@ start() { return 1 } -# TODO: extract the config-creation from here -update_one() { - ping -c 1 -t 2 $1 >/dev/null - PINGRESULT=$? - if [ $PINGRESULT -eq 0 ]; then - echo Updating $1 - curl -s http://$1/presets.json -o presets-tmp.json >/dev/null - curl -s http://$1/cfg.json -o cfg-tmp.json >/dev/null +update_batch() { + airport -s | grep WLED | cut -c23-32 | while read line + do + if [ "$line" == "WLED-AP" ]; then + update_one 4.3.2.1 "$line" "wled1234" + else + update_one 4.3.2.1 "$line" "WledWled" + fi + done +} - update_firmware $1 - curl -s -F "data=@cfg-tmp.json;filename=/cfg.json" http://$1/upload >/dev/null - curl -s -F "data=@presets-tmp.json;filename=/presets.json" http://$1/upload >/dev/null - curl -s http://$1/reset >/dev/null - rm presets-tmp.json - rm cfg-tmp.json - return 0 +process() { + if [ "$1" == "upload" ]; then + upload_firmware + elif [ "$1" == "batch" ]; then + update_batch else - echo Missing $1 + while : + do + update_one 4.3.2.1 "WLED-AP" "wled1234" + done fi - return 1 } -while : -do - start 4.3.2.1 -done +process "$@" diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 0fc0ba05c2..07bc07c415 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -138,14 +138,8 @@ class LightNode { strcpy(clientPass, ""); #endif - // Try to hide the access point unless this is the "root" node - if (this->is_following()) { - sprintf(apSSID, "WLED %03X F", this->header.id); - } else { - sprintf(apSSID, "WLED %03X", this->header.id); - } - strcpy(apPass, "WledWled"); - apBehavior = AP_BEHAVIOR_NO_CONN; + // By default, we don't want these visible. + apBehavior = AP_BEHAVIOR_BUTTON_ONLY; // Must press button for 6 seconds to get AP } void start() { diff --git a/usermods/Tubes/update_server.py b/usermods/Tubes/update_server.py index 3bb1e4d624..2cea859c69 100644 --- a/usermods/Tubes/update_server.py +++ b/usermods/Tubes/update_server.py @@ -266,7 +266,7 @@ def test(HandlerClass = SimpleHTTPRequestHandler, httpd = BaseHTTPServer.HTTPServer(server_address, SimpleHTTPRequestHandler) sa = httpd.socket.getsockname() - print "Serving HTTP on", sa[0], "port", sa[1], "..." + print("Serving HTTP on", sa[0], "port", sa[1], "...") httpd.serve_forever() BaseHTTPServer.test(HandlerClass, ServerClass) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 6d475cb969..b048bc3812 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -31,9 +31,12 @@ typedef struct AutoUpdateOffer { class AutoUpdater { public: - AutoUpdateOffer location; + AutoUpdateOffer current_version; + + // For now, hardcode it all + String host_name = "brcac.com"; String path = "/firmware.bin"; - int port = 8000; + int port = 80; long fileSize = 0; @@ -67,17 +70,24 @@ class AutoUpdater { } } - void start() { + void start(AutoUpdateOffer *new_version) { if (this->status != Idle) { log("update already in progress."); return; } + if (new_version->version <= current_version.version) { + log("don't need to update to that version."); + return; + } + // The auto-update process might break the current connection _storedSSID = String(clientSSID); _storedPass = String(clientPass); WLED::instance().disableWatchdog(); - + + memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); + log("starting autoupdate"); this->status = Started; } @@ -111,9 +121,11 @@ class AutoUpdater { auto s = WiFi.status(); switch (s) { case WL_DISCONNECTED: - log("connecting to autoupdate server"); - strcpy(clientSSID, this->location.ssid); - strcpy(clientPass, this->location.password); + if (!strlen(clientSSID)) { + log("connecting to autoupdate server"); + strcpy(clientSSID, this->current_version.ssid); + strcpy(clientPass, this->current_version.password); + } return; case WL_NO_SSID_AVAIL: @@ -125,7 +137,7 @@ class AutoUpdater { return; case WL_CONNECTED: - if (WiFi.SSID() != String(this->location.ssid)) { + if (WiFi.SSID() != String(this->current_version.ssid)) { log("disconnecting from WiFi"); WiFi.disconnect(false, true); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; @@ -136,6 +148,12 @@ class AutoUpdater { this->status = Connected; return; + case WL_IDLE_STATUS: + EVERY_N_MILLIS(300) { + Serial.print("..."); + } + break; + default: Serial.printf("OTA: wifi %d", (int)s); break; @@ -144,16 +162,18 @@ class AutoUpdater { void do_request() { log("connecting"); - if (!this->_client.connect(this->location.host, this->port)) { + if (!this->_client.connect(this->host_name.c_str(), this->port)) { // this->current_version.host abort("connect failed"); return; } // Get the contents of the bin file log("requesting update package"); - this->_client.print(String("GET ") + this->path + " HTTP/1.1\r\n" + - "Host: " + this->location.host + "\r\n" + - "Cache-Control: no-cache\r\n\r\n"); + auto request = String("GET ") + this->path + " HTTP/1.1\r\n" + + "Host: " + this->host_name + "\r\n" + + "Cache-Control: no-cache\r\n\r\n"; + Serial.println(request); + this->_client.print(request); log("awaiting response"); timeoutTimer.start(5000); diff --git a/wled00/button.cpp b/wled00/button.cpp index b251a0c4a6..564b7f1161 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -8,8 +8,10 @@ #define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms #define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press #define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 -#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP -#define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset +#define WLED_LONG_AP 3000 // how long button 0 needs to be held to activate WLED-AP + +// SteveE: geez you can't put factory reset so close to "just turn on the AP" +#define WLED_LONG_FACTORY_RESET 30000 // how long button 0 needs to be held to trigger a factory reset static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage From 20d2056e3fa75045673c5816b219a039f30c1dbe Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 02:21:05 -0700 Subject: [PATCH 163/263] add roles and master commands --- usermods/Tubes/Tubes.h | 35 ++++++-- usermods/Tubes/controller.h | 143 ++++++++++++++++++++++++------ usermods/Tubes/debug.h | 16 ++-- usermods/Tubes/global_state.h | 1 + usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/master.h | 162 ++++++++++++++++++++++++++++++++++ usermods/Tubes/node.h | 2 + usermods/Tubes/updater.h | 14 +-- 8 files changed, 330 insertions(+), 45 deletions(-) create mode 100644 usermods/Tubes/master.h diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index c5bda68df5..5211ae5eeb 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -1,14 +1,11 @@ #pragma once +#include #include "wled.h" #include "util.h" #include "options.h" -// #define MASTERCONTROL - -#define MASTER_PIN 6 - // #define USERADIO #include "FX.h" @@ -16,17 +13,23 @@ #include "beats.h" #include "virtual_strip.h" #include "led_strip.h" +#include "master.h" #include "controller.h" #include "debug.h" +#define MASTER_PIN 25 +#define LEGACY_PIN 32 // DigUno Q4 + + class TubesUsermod : public Usermod { private: BeatController beats; PatternController controller = PatternController(MAX_REAL_LEDS, &beats); DebugController debug = DebugController(&controller); - int* master = NULL; /* master.h deleted */ + Master *master = nullptr; + bool isLegacy = false; void randomize() { randomSeed(esp_random()); @@ -36,6 +39,16 @@ class TubesUsermod : public Usermod { public: void setup() { + ControllerRole role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + if (role == MasterRole) { + master = new Master(&controller); + } + + pinMode(MASTER_PIN, INPUT_PULLUP); + pinMode(LEGACY_PIN, INPUT_PULLUP); + if (digitalRead(MASTER_PIN) == LOW) { + } + isLegacy = (digitalRead(MASTER_PIN) == LOW); randomize(); // Override some behaviors on all Tubes @@ -48,7 +61,11 @@ class TubesUsermod : public Usermod { // Start timing globalTimer.setup(); beats.setup(); - controller.setup(0); + controller.setup(); + if (controller.isMaster()) { + master = new Master(&controller); + master->setup(); + } debug.setup(); } @@ -58,18 +75,22 @@ class TubesUsermod : public Usermod { randomize(); } + if (master) + master->update(); beats.update(); controller.update(); debug.update(); // Draw after everything else is done - controller.led_strip->update(master != NULL); // ~25us + controller.led_strip->update(); } void handleOverlayDraw() { // Draw effects layers over whatever WLED is doing. this->controller.handleOverlayDraw(); + // if (master) + // master->handleOverlayDraw(); this->debug.handleOverlayDraw(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index f7a37be542..933cb1dd4c 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -22,6 +22,7 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define MIN_COLOR_CHANGE_PHRASES 2 // 4 #define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define ROLE_EEPROM_LOCATION 2559 #define IDENTIFY_STUCK_PATTERNS @@ -38,6 +39,18 @@ typedef struct { TubeState next; } TubeStates; +typedef enum ControllerRole : uint8_t { + DefaultRole = 10, // Turn on in power saving mode + InstallationRole = 20, // Disable power-saving mode + LegacyRole = 100, // 1/2 the pixels + MasterRole = 200 // Controls all the others +} ControllerRole; + +typedef struct { + char key; + uint8_t arg; +} Action; + #define NUM_VSTRIPS 3 #define DEBOUNCE_TIME 40 @@ -80,10 +93,10 @@ class PatternController : public MessageReceiver { uint8_t num_leds; VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; - bool isMaster = false; uint8_t paletteOverride = 0; uint8_t patternOverride = 0; uint16_t wled_fader = 0; + ControllerRole role; AutoUpdater auto_updater = AutoUpdater(); @@ -129,17 +142,35 @@ class PatternController : public MessageReceiver { #endif } } - - void setup(bool isMaster) + + bool isMaster() { + return this->role == MasterRole; + } + + void setup() { this->node->setup(); - this->isMaster = isMaster; - this->options.power_save = true; + this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + this->options.power_save = false; this->options.debugging = false; - if (isMaster) - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; - else - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + switch (role) { + case MasterRole: + this->node->reset(4050); // MASTER ID + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + break; + + case LegacyRole: + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + this->options.power_save = false; + break; + + default: + this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + this->options.power_save = true; + break; + } this->load_options(this->options); #ifdef USELCD @@ -315,13 +346,14 @@ class PatternController : public MessageReceiver { } } - if (this->options.power_save) { + // Power Save mode: reduce number of displayed pixels + // Only affects non-powered poles + if (this->options.power_save && this->role == DefaultRole) { // Screen door effectn uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { - CRGB c = strip.getPixelColor(i); - strip.setPixelColor(i, CRGB(c.r>>3,c.g>>3,c.b>>3)); + strip.setPixelColor(i, CRGB::Black); } } } @@ -635,6 +667,14 @@ class PatternController : public MessageReceiver { this->options.power_save = power_save; this->broadcast_options(); } + + void setRole(ControllerRole role) { + this->role = role; + Serial.printf("Role = %d", role); + EEPROM.write(ROLE_EEPROM_LOCATION, role); + ESP.restart(); + } + SyncMode randomSyncMode() { uint8_t r = random8(128); @@ -752,11 +792,6 @@ class PatternController : public MessageReceiver { case '~': ESP.restart(); break; - case 'M': - // Cancel any overrides - this->paletteOverrideTimer.stop(); - this->patternOverrideTimer.stop(); - break; case '-': b = this->options.brightness; @@ -836,17 +871,22 @@ class PatternController : public MessageReceiver { this->node->reset(arg >> 4); return; + case 'V': case 'g': - for (int i=0; i< 10; i++) - addGlitter(); + case 'A': + case 'W': + case 'X': + case 'M': { + Action action = { + .key = command[0], + .arg = (uint8_t)(arg >> 8) + }; + this->broadcast_action(action); return; + } - case 'W': - // Clear wifi - Serial.print("Clearing WiFi connection."); - strcpy(clientSSID, ""); - strcpy(clientPass, ""); - WiFi.disconnect(false, true); + case 'R': + this->setRole((ControllerRole)(arg >> 8)); return; case '?': @@ -894,6 +934,13 @@ class PatternController : public MessageReceiver { this->broadcast_state(); } + void broadcast_action(Action& action) { + if (!this->node->is_following()) { + this->onAction(&action); + } + this->node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); + } + void broadcast_state() { this->node->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(TubeStates)); } @@ -904,7 +951,7 @@ class PatternController : public MessageReceiver { void broadcast_autoupdate() { AutoUpdateOffer offer; - this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.location, sizeof(this->auto_updater.location)); + this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.current_version, sizeof(this->auto_updater.current_version)); } virtual void onCommand(CommandId command, void *data) { @@ -942,9 +989,53 @@ class PatternController : public MessageReceiver { case COMMAND_UPGRADE: this->auto_updater.start((AutoUpdateOffer*)data); return; + + case COMMAND_ACTION: + this->onAction((Action*)data); + return; } Serial.printf("UNKNOWN COMMAND %02X", command); } + void onAction(Action* action) { + switch (action->key) { + case 'A': + Serial.println("Turning on WiFi access point."); + WLED::instance().initAP(true); + return; + + case 'X': + ESP.restart(); + return; + + case 'W': + Serial.println("Clearing WiFi connection."); + strcpy(clientSSID, ""); + strcpy(clientPass, ""); + WiFi.disconnect(false, true); + return; + + case 'g': + Serial.println("glitter!"); + for (int i=0; i< 10; i++) + addGlitter(); + return; + + case 'M': + Serial.println("cancel manual mode"); + this->paletteOverrideTimer.stop(); + this->patternOverrideTimer.stop(); + break; + + case 'V': + // Version check: try to update, but leave wifi on for the script + if (this->auto_updater.current_version.version < action->arg) { + WLED::instance().initAP(true); + this->auto_updater.start(); + } + break; + } + } + }; diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index a15998cd30..34aba31dfd 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -76,6 +76,10 @@ class DebugController { formatted_time(millis()).c_str() ); + if (this->controller->isMaster()) { + Serial.print("=== PRIMARY CONTROLER\n"); + } + // Dump WLED status char mode_name[50]; char palette_name[50]; @@ -101,13 +105,13 @@ class DebugController { Serial.println(); Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", - this->controller->auto_updater.location.version, + this->controller->auto_updater.current_version.version, this->controller->auto_updater.status, - this->controller->auto_updater.location.ssid, - this->controller->auto_updater.location.host[0], - this->controller->auto_updater.location.ssid[1], - this->controller->auto_updater.location.ssid[2], - this->controller->auto_updater.location.ssid[3] + this->controller->auto_updater.current_version.ssid, + this->controller->auto_updater.current_version.host[0], + this->controller->auto_updater.current_version.ssid[1], + this->controller->auto_updater.current_version.ssid[2], + this->controller->auto_updater.current_version.ssid[3] ); } diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index 0e1af1c008..e86839512e 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -57,5 +57,6 @@ typedef uint8_t CommandId; const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_UPDATE = 0x20; +const static CommandId COMMAND_ACTION = 0x30; const static CommandId COMMAND_UPGRADE = 0xE0; const static CommandId COMMAND_RESET = 0xF0; \ No newline at end of file diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index a15ddfaf47..96830f87e8 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -33,7 +33,7 @@ class LEDs { } } - void update(bool reverse=false) { + void update() { EVERY_N_MILLISECONDS( this->TARGET_REFRESH ) { this->fps++; } diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h new file mode 100644 index 0000000000..0e7ab99a5d --- /dev/null +++ b/usermods/Tubes/master.h @@ -0,0 +1,162 @@ +#pragma once + +#include "timer.h" +#include "controller.h" +#include "led_strip.h" + +#define X_AXIS_PIN 20 +#define Y_AXIS_PIN 21 + +#define BUTTON_PIN_1 70 // SELECT? +#define BUTTON_PIN_2 71 // SKIP +#define BUTTON_PIN_3 72 // SET COLOR +#define BUTTON_PIN_4 23 // TAP +#define BUTTON_PIN_5 73 + + +class Master { + public: + uint8_t taps=0; + Timer tapTimer; + uint16_t tapTime[16]; + + Background background; + uint8_t palette_mode = false; + uint8_t palette_id = 0; + + PatternController *controller; + Button button[5]; + + Master(PatternController *controller) { + this->controller = controller; + } + + void setup() { + this->button[0].setup(BUTTON_PIN_1); + this->button[1].setup(BUTTON_PIN_2); + this->button[2].setup(BUTTON_PIN_3); + this->button[3].setup(BUTTON_PIN_4); + this->button[4].setup(BUTTON_PIN_5); + Serial.println((char *)F("Master: ok")); + } + + void update() { + for (uint8_t i=0; i < 5; i++) { + if (button[i].triggered()) { + if (button[i].pressed()) + this->onButtonPress(i); + else + this->onButtonRelease(i); + } + } + + if (this->taps && this->tapTimer.since_mark() > 10000) { + this->taps = 0; + this->fail(); + } + + this->updateStatus(this->controller, this->controller->led_strip); + } + + void ok() { + addFlash(CRGB::Green); + } + + void fail() { + addFlash(CRGB::Red); + } + + void onButtonPress(uint8_t button) { + if (button == 0) + return; + + if (button == 1) { + Serial.println((char *)F("Skip >>")); + this->controller->force_next(); + this->ok(); + return; + } + + if (button == 3) { + this->tap(); + return; + } + + Serial.print((char *)F("Pressed ")); + Serial.println(button); + } + + void onButtonRelease(uint8_t button) { +#ifdef EXTRA_STUFF + if (button == 2) { + if (this->palette_mode) + this->controller->_load_palette(this->palette_id); + this->palette_mode = false; + } +#endif + + if (button == 3) { + if (this->taps == 0) + return; + this->tap(); + return; + } + + Serial.print((char *)F("Released ")); + Serial.println(button); + } + + void tap() { + Serial.println((char *)F("tap")); + if (!this->taps) { + this->tapTimer.start(0); + } + + uint32_t time = this->tapTimer.since_mark(); + this->tapTime[this->taps++] = time; + + uint32_t bpm = 0; + if (this->taps > 4) { + // Can study this later to make BPM detection better + + bpm = 60000*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat + if (bpm < 70*256) + bpm *= 2; + else if (bpm > 140*256) + bpm /= 2; + } + + if (this->taps == 16) { + Serial.println("OK! taps"); + this->taps = 0; + this->controller->set_tapped_bpm(bpm); + this->ok(); + } + } + + void updateStatus(PatternController *controller, LEDs *strip) { + if (this->taps) { + this->displayProgress(this->taps); + } else if (this->palette_mode) { + this->displayPalette(this->background); + } else { + uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; + strip->leds[beat_pos] = CRGB::White; + } + } + + void displayProgress(uint8_t progress) { + fill_solid(this->controller->led_strip->leds, 16, CRGB::Black); + fill_solid(this->controller->led_strip->leds, progress % 16, CRGB(128,128,128)); + } + + void displayPalette(Background &background) { + for (int i = 0; i < 16; i++) { + Segment& segment = strip.getMainSegment(); + uint32_t color = CRGB(segment.color_from_palette(i * 16, false, true, 255)); + this->controller->led_strip->leds[i] = color; + } + } + +}; + diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 07bc07c415..a36876f7c2 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -58,6 +58,8 @@ const char *command_name(CommandId command) { return "OPTIONS"; case COMMAND_RESET: return "RESET"; + case COMMAND_ACTION: + return "ACTION"; default: return "?COMMAND?"; } diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index b048bc3812..b73d057b64 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 3 +#define RELEASE_VERSION 4 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { @@ -70,13 +70,13 @@ class AutoUpdater { } } - void start(AutoUpdateOffer *new_version) { + void start(AutoUpdateOffer *new_version = nullptr) { if (this->status != Idle) { log("update already in progress."); return; } - if (new_version->version <= current_version.version) { + if (new_version && new_version->version <= current_version.version) { log("don't need to update to that version."); return; } @@ -86,7 +86,9 @@ class AutoUpdater { _storedPass = String(clientPass); WLED::instance().disableWatchdog(); - memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); + if (new_version) { + memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); + } log("starting autoupdate"); this->status = Started; @@ -161,6 +163,7 @@ class AutoUpdater { } void do_request() { + WLED::instance().disableWatchdog(); log("connecting"); if (!this->_client.connect(this->host_name.c_str(), this->port)) { // this->current_version.host abort("connect failed"); @@ -238,9 +241,10 @@ class AutoUpdater { return; }; + WLED::instance().disableWatchdog(); this->progress = 0; vTaskDelay(500); - uint8_t buf[2048]; + uint8_t buf[512]; int lr; while ((lr = client.read(buf, sizeof(buf))) > 0) { size_t written = Update.write(buf, lr); From c4435e852fd3f9af0e3dc0ce27bf0842e38e2de3 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 04:09:17 -0700 Subject: [PATCH 164/263] Fix master mode and tempo tapping --- usermods/Tubes/Tubes.h | 10 ++-------- usermods/Tubes/controller.h | 10 ++++++++-- usermods/Tubes/master.h | 29 ++++++++++++++++++++++------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 5211ae5eeb..f898600b89 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -1,6 +1,5 @@ #pragma once -#include #include "wled.h" #include "util.h" @@ -39,11 +38,6 @@ class TubesUsermod : public Usermod { public: void setup() { - ControllerRole role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); - if (role == MasterRole) { - master = new Master(&controller); - } - pinMode(MASTER_PIN, INPUT_PULLUP); pinMode(LEGACY_PIN, INPUT_PULLUP); if (digitalRead(MASTER_PIN) == LOW) { @@ -89,8 +83,8 @@ class TubesUsermod : public Usermod { { // Draw effects layers over whatever WLED is doing. this->controller.handleOverlayDraw(); - // if (master) - // master->handleOverlayDraw(); + if (master) + master->handleOverlayDraw(); this->debug.handleOverlayDraw(); } }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 933cb1dd4c..fc4fd0fd9f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1,5 +1,6 @@ #pragma once +#include #include "wled.h" #include "FX.h" #include "updater.h" @@ -13,7 +14,7 @@ #include "global_state.h" #include "node.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 100; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE @@ -150,7 +151,10 @@ class PatternController : public MessageReceiver { void setup() { this->node->setup(); + EEPROM.begin(2560); this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + EEPROM.end(); + Serial.printf("Role = %d", this->role); this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; this->options.power_save = false; @@ -671,10 +675,12 @@ class PatternController : public MessageReceiver { void setRole(ControllerRole role) { this->role = role; Serial.printf("Role = %d", role); + EEPROM.begin(2560); EEPROM.write(ROLE_EEPROM_LOCATION, role); + EEPROM.end(); + delay(10); ESP.restart(); } - SyncMode randomSyncMode() { uint8_t r = random8(128); diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 0e7ab99a5d..14fb9f8ecc 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -54,8 +54,10 @@ class Master { this->taps = 0; this->fail(); } + } - this->updateStatus(this->controller, this->controller->led_strip); + void handleOverlayDraw() { + this->updateStatus(this->controller); } void ok() { @@ -129,32 +131,45 @@ class Master { if (this->taps == 16) { Serial.println("OK! taps"); this->taps = 0; + auto frac = bpm % 256; + + // Slight snap to beat + if (frac < 128) + bpm -= frac / 2; + else if (frac > 128) + bpm += (256-frac) / 2; + this->controller->set_tapped_bpm(bpm); this->ok(); } } - void updateStatus(PatternController *controller, LEDs *strip) { + void updateStatus(PatternController *controller) { if (this->taps) { this->displayProgress(this->taps); } else if (this->palette_mode) { this->displayPalette(this->background); } else { uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; - strip->leds[beat_pos] = CRGB::White; + strip.setPixelColor(16 - beat_pos, CRGB::White); } } void displayProgress(uint8_t progress) { - fill_solid(this->controller->led_strip->leds, 16, CRGB::Black); - fill_solid(this->controller->led_strip->leds, progress % 16, CRGB(128,128,128)); + for (int i = 0; i < 16; i++) { + if (i < progress % 16) { + strip.setPixelColor(16 - i, CRGB(128,128,128)); + } else { + strip.setPixelColor(16 - i, CRGB::Black); + } + } } void displayPalette(Background &background) { for (int i = 0; i < 16; i++) { Segment& segment = strip.getMainSegment(); - uint32_t color = CRGB(segment.color_from_palette(i * 16, false, true, 255)); - this->controller->led_strip->leds[i] = color; + auto color = CRGB(segment.color_from_palette(i * 16, false, true, 255)); + strip.setPixelColor(i, color); } } From 1f0199f44ac57b509c227de87634671bbad227d3 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 16:02:37 -0700 Subject: [PATCH 165/263] New autoupdate modes --- usermods/Tubes/Tubes.h | 8 +++- usermods/Tubes/bluetooth.h | 2 +- usermods/Tubes/controller.h | 84 ++++++++++++++--------------------- usermods/Tubes/debug.h | 21 +++++---- usermods/Tubes/firmware.sh | 11 ++++- usermods/Tubes/global_state.h | 4 +- usermods/Tubes/node.h | 44 +++++++++++------- usermods/Tubes/updater.h | 82 +++++++++++++++++++++++++++++----- 8 files changed, 164 insertions(+), 92 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index f898600b89..57848a7a27 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -83,8 +83,14 @@ class TubesUsermod : public Usermod { { // Draw effects layers over whatever WLED is doing. this->controller.handleOverlayDraw(); + this->debug.handleOverlayDraw(); if (master) master->handleOverlayDraw(); - this->debug.handleOverlayDraw(); + + // When AP mode is on, make sure it's obvious + if (apActive) { + strip.setPixelColor(0, CRGB::Purple); + strip.setPixelColor(1, CRGB::Black); + } } }; diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index 0322264939..d98f5bcf88 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -448,7 +448,7 @@ class BLEMeshNode: public NimBLEAdvertisedDeviceCallbacks { // Process the command this->receiver->onCommand( storage.header.id, - COMMAND_UPDATE, + COMMAND_STATE, &storage.current ); this->receiver->onCommand( diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index fc4fd0fd9f..9a5f5cdb7d 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -99,7 +99,7 @@ class PatternController : public MessageReceiver { uint16_t wled_fader = 0; ControllerRole role; - AutoUpdater auto_updater = AutoUpdater(); + AutoUpdater updater = AutoUpdater(); Timer graphicsTimer; Timer updateTimer; @@ -311,7 +311,7 @@ class PatternController : public MessageReceiver { } } - this->auto_updater.update(); + this->updater.update(); #ifdef USELCD if (this->lcd->active) { @@ -368,42 +368,7 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } - drawOTAOverlay(); - - // When AP mode is on, make sure it's obvious - if (apActive) { - strip.setPixelColor(0, CRGB::Purple); - strip.setPixelColor(1, CRGB::Black); - } - } - - void drawOTAOverlay() { - CRGB c; - switch (this->auto_updater.status) { - case Started: - case Connected: - case Received: - c = CRGB::Yellow; - if (millis() % 1000 < 500) { - c = CRGB::Black; - } - break; - - case Failed: - c = CRGB::Red; - break; - - case Complete: - c = CRGB::Green; - break; - - case Idle: - default: - return; - } - for (int i = 0; i < 20; i++) { - strip.setPixelColor(i, c); - } + this->updater.handleOverlayDraw(); } void restart_phrase() { @@ -903,7 +868,6 @@ class PatternController : public MessageReceiver { Serial.println(F("m### - sync mode")); Serial.println(F("c### - colors")); Serial.println(F("e### - effects")); - Serial.println("w### - wled pattern"); Serial.println(); Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); @@ -911,10 +875,16 @@ class PatternController : public MessageReceiver { Serial.println("@ - power save mode"); Serial.println("U - begin auto-update"); Serial.println("O - offer an auto-update"); + Serial.println("==== global actions ===="); + Serial.println("A - turn on access point"); + Serial.println("W - forget WiFi client"); + Serial.println("X - restart"); + Serial.println("V### - auto-upgrade to version"); + Serial.println("M - cancel manual pattern override"); return; case 'U': - this->auto_updater.start(); + this->updater.start(); return; case 'O': @@ -947,8 +917,12 @@ class PatternController : public MessageReceiver { this->node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); } + void broadcast_info(NodeInfo *info) { + this->node->sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); + } + void broadcast_state() { - this->node->sendCommand(COMMAND_UPDATE, &this->current_state, sizeof(TubeStates)); + this->node->sendCommand(COMMAND_STATE, &this->current_state, sizeof(TubeStates)); } void broadcast_options() { @@ -956,14 +930,15 @@ class PatternController : public MessageReceiver { } void broadcast_autoupdate() { - AutoUpdateOffer offer; - this->node->sendCommand(COMMAND_UPGRADE, &this->auto_updater.current_version, sizeof(this->auto_updater.current_version)); + this->node->sendCommand(COMMAND_UPGRADE, &this->updater.current_version, sizeof(this->updater.current_version)); } virtual void onCommand(CommandId command, void *data) { switch (command) { - case COMMAND_RESET: - // TODO + case COMMAND_INFO: + Serial.printf(" \"%s\"\n", + ((NodeInfo*)data)->message + ); return; case COMMAND_OPTIONS: @@ -975,7 +950,7 @@ class PatternController : public MessageReceiver { ); return; - case COMMAND_UPDATE: { + case COMMAND_STATE: { auto update_data = (TubeStates*)data; TubeState state; @@ -993,7 +968,7 @@ class PatternController : public MessageReceiver { } case COMMAND_UPGRADE: - this->auto_updater.start((AutoUpdateOffer*)data); + this->updater.start((AutoUpdateOffer*)data); return; case COMMAND_ACTION: @@ -1035,12 +1010,19 @@ class PatternController : public MessageReceiver { break; case 'V': - // Version check: try to update, but leave wifi on for the script - if (this->auto_updater.current_version.version < action->arg) { - WLED::instance().initAP(true); - this->auto_updater.start(); + // Version check: prepare for update + if (this->updater.current_version.version >= action->arg) + break; + + this->updater.ready(); + break; + + case 'U': + if (this->updater.status == Ready) { + this->updater.start(); } break; + } } diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 34aba31dfd..c05977ae4d 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -76,9 +76,12 @@ class DebugController { formatted_time(millis()).c_str() ); + Serial.printf("=== Controller: "); if (this->controller->isMaster()) { - Serial.print("=== PRIMARY CONTROLER\n"); + Serial.print("PRIMARY "); } + Serial.printf("role=%d\n", + this->controller->role); // Dump WLED status char mode_name[50]; @@ -104,14 +107,14 @@ class DebugController { } Serial.println(); - Serial.printf("=== OTA: v%d state %d SSID %s %u.%u.%u.%u \n\n", - this->controller->auto_updater.current_version.version, - this->controller->auto_updater.status, - this->controller->auto_updater.current_version.ssid, - this->controller->auto_updater.current_version.host[0], - this->controller->auto_updater.current_version.ssid[1], - this->controller->auto_updater.current_version.ssid[2], - this->controller->auto_updater.current_version.ssid[3] + Serial.printf("=== firmware: v%d from SSID %s %u.%u.%u.%u OTA=%d\n\n", + this->controller->updater.current_version.version, + this->controller->updater.current_version.ssid, + this->controller->updater.current_version.host[0], + this->controller->updater.current_version.ssid[1], + this->controller->updater.current_version.ssid[2], + this->controller->updater.current_version.ssid[3], + this->controller->updater.status ); } diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 38e7d26d4a..5d204756ee 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -9,7 +9,7 @@ ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools upload_firmware() { echo "Uploading firmware" sftp control@brcac.com <header.uplinkId, command_name(message->command) ); - if (message->recipients == ROOT) + if (message->recipients == RECIPIENTS_ROOT) Serial.printf(":ROOT"); if (rssi) Serial.printf(" %ddB ", rssi); @@ -217,16 +223,20 @@ class LightNode { bool ignore = false; switch (message->recipients) { - case ALL: + case RECIPIENTS_ALL: // Ignore this message if not from the uplink ignore = (message->header.id != this->header.uplinkId); break; - case ROOT: - // Ignore this message if not from a downlink + case RECIPIENTS_ROOT: + // Ignore this message if not from one of this node's downlinks ignore = (message->header.uplinkId != this->header.id); break; + case RECIPIENTS_INFO: + ignore = false; + break; + default: // ignore this! ignore = true; @@ -243,7 +253,7 @@ class LightNode { } // Execute the received command - if (message->recipients != ROOT || !this->is_following()) { + if (message->recipients != RECIPIENTS_ROOT || !this->is_following()) { Serial.print(" >> "); print_message(message, rssi); Serial.print(" "); @@ -264,10 +274,10 @@ class LightNode { } // Re-broadcast the message if appropriate - if (!this->rebroadcastTimer.ended()) { + if (!this->rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { message->header = this->header; if (!this->is_following()) - message->recipients = ALL; + message->recipients = RECIPIENTS_ALL; this->broadcast(message, true); } } @@ -301,11 +311,15 @@ class LightNode { NodeMessage message; message.header = header; - if (command != COMMAND_UPDATE && this->is_following()) { + if (command == COMMAND_INFO) { + message.recipients = RECIPIENTS_INFO; + } else if (command == COMMAND_STATE) { + message.recipients = RECIPIENTS_ALL; + } else if (this->is_following()) { // Follower nodes must request that the root re-sends this message - message.recipients = ROOT; + message.recipients = RECIPIENTS_ROOT; } else { - message.recipients = ALL; + message.recipients = RECIPIENTS_ALL; } message.command = command; memcpy(&message.data, data, len); diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index b73d057b64..2d0519fd5e 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -15,9 +15,10 @@ String getHeaderValue(String header, String headerName) { typedef enum UpdateWorkflowStatus: uint8_t { Idle=0, - Started=1, - Connected=2, - Received=4, + Ready=10, + Started=50, + Connected=60, + Received=80, Complete=100, Failed=101, } UpdateWorkflowStatus; @@ -42,20 +43,31 @@ class AutoUpdater { String _storedSSID = ""; String _storedPass = ""; + String _storedAPSSID = ""; + String _storedAPPass = ""; int progress = 0; WiFiClient _client; UpdateWorkflowStatus status = Idle; Timer timeoutTimer; - Timer progressTimer; + Timer displayStatusTimer; void update() { switch (this->status) { case Complete: + if (this->displayStatusTimer.ended()) { + doReboot = true; + this->status = Idle; + } + return; + case Failed: - if (this->progressTimer.ended()) + if (this->displayStatusTimer.ended()) this->status = Idle; + return; + + case Ready: case Idle: case Received: return; @@ -84,7 +96,9 @@ class AutoUpdater { // The auto-update process might break the current connection _storedSSID = String(clientSSID); _storedPass = String(clientPass); - WLED::instance().disableWatchdog(); + _storedAPSSID = String(apSSID); + _storedAPPass = String(apPass); + otaLock = false; if (new_version) { memcpy((byte*)&this->current_version, new_version, sizeof(this->current_version)); @@ -92,17 +106,61 @@ class AutoUpdater { log("starting autoupdate"); this->status = Started; + this->displayStatusTimer.stop(); + } + + void ready() { + log("ready for update - turning on updater AP"); + strcpy(apSSID, "WLED-UPDATE"); + strcpy(apPass, "update1234"); + WLED::instance().initAP(true); + this->status = Ready; } void stop() { this->_client.stop(); strcpy(clientSSID, _storedSSID.c_str()); strcpy(clientPass, _storedPass.c_str()); + strcpy(apSSID, _storedAPSSID.c_str()); + strcpy(apPass, _storedAPPass.c_str()); WiFi.disconnect(false, true); WLED::instance().enableWatchdog(); this->status = Idle; } + void handleOverlayDraw() { + CRGB c; + switch (this->status) { + case Ready: + c = CRGB::Purple; + break; + + case Started: + case Connected: + case Received: + c = CRGB::Yellow; + if (millis() % 1000 < 500) { + c = CRGB::Black; + } + break; + + case Failed: + c = CRGB::Red; + break; + + case Complete: + c = CRGB::Green; + break; + + case Idle: + default: + return; + } + for (int i = 0; i < 20; i++) { + strip.setPixelColor(i, c); + } + } + private: void log(const char *message) { Serial.printf("OTA: %s\n", message); @@ -114,9 +172,9 @@ class AutoUpdater { void abort(const char *message) { log(message); - this->status = Failed; - this->progressTimer.start(10000); this->stop(); + this->status = Failed; + this->displayStatusTimer.start(30000); } void do_connect() { @@ -244,7 +302,7 @@ class AutoUpdater { WLED::instance().disableWatchdog(); this->progress = 0; vTaskDelay(500); - uint8_t buf[512]; + uint8_t buf[4096]; int lr; while ((lr = client.read(buf, sizeof(buf))) > 0) { size_t written = Update.write(buf, lr); @@ -270,10 +328,10 @@ class AutoUpdater { return; } - doReboot = true; log("update successfully completed. Rebooting."); - this->status = Complete; - this->progressTimer.start(10000); this->stop(); + this->status = Complete; + this->displayStatusTimer.start(10000); } + }; From 21442e51f58ba9648966aac202f0b9f888a73034 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 23 Aug 2022 16:58:08 -0700 Subject: [PATCH 166/263] Better power save mode with button override --- usermods/Tubes/Tubes.h | 11 ++++++++ usermods/Tubes/controller.h | 54 +++++++++++++++++++++++++------------ usermods/Tubes/debug.h | 6 +++-- wled00/button.cpp | 11 ++++++-- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 57848a7a27..48f7e6027d 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -93,4 +93,15 @@ class TubesUsermod : public Usermod { strip.setPixelColor(1, CRGB::Black); } } + + bool handleButton(uint8_t b) { + // Special code for handling the "power save" button + if (b == 100) { + this->controller.togglePowerSave(); + return true; + } + + return false; + } + }; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 9a5f5cdb7d..a926d609a5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -29,7 +29,6 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; typedef struct { bool debugging; - bool power_save; uint8_t brightness; uint8_t reserved[12]; @@ -41,9 +40,11 @@ typedef struct { } TubeStates; typedef enum ControllerRole : uint8_t { + UnknownRole = 0, DefaultRole = 10, // Turn on in power saving mode - InstallationRole = 20, // Disable power-saving mode - LegacyRole = 100, // 1/2 the pixels + CampRole = 50, // Turn on in non-power-saving mode + InstallationRole = 100, // Disable power-saving mode completely + LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary MasterRole = 200 // Controls all the others } ControllerRole; @@ -98,6 +99,7 @@ class PatternController : public MessageReceiver { uint8_t patternOverride = 0; uint16_t wled_fader = 0; ControllerRole role; + bool power_save = true; // Power save ALWAYS starts on. Some roles just ignore it AutoUpdater updater = AutoUpdater(); @@ -153,11 +155,14 @@ class PatternController : public MessageReceiver { this->node->setup(); EEPROM.begin(2560); this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + if (this->role == 255) { + this->role = DefaultRole; + } EEPROM.end(); Serial.printf("Role = %d", this->role); + this->power_save = (this->role < CampRole); this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.power_save = false; this->options.debugging = false; switch (role) { case MasterRole: @@ -167,12 +172,10 @@ class PatternController : public MessageReceiver { case LegacyRole: this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.power_save = false; break; default: this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.power_save = true; break; } this->load_options(this->options); @@ -352,8 +355,8 @@ class PatternController : public MessageReceiver { // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles - if (this->options.power_save && this->role == DefaultRole) { - // Screen door effectn + if (this->power_save && this->role < InstallationRole) { + // Screen door effect to save power uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { @@ -630,11 +633,13 @@ class PatternController : public MessageReceiver { this->broadcast_options(); } + void togglePowerSave() { + setPowerSave(!this->power_save); + } + void setPowerSave(bool power_save) { Serial.printf("power_save: %d\n", power_save); - - this->options.power_save = power_save; - this->broadcast_options(); + this->power_save = power_save; } void setRole(ControllerRole role) { @@ -757,13 +762,13 @@ class PatternController : public MessageReceiver { case 'd': this->setDebugging(!this->options.debugging); break; - case '@': - this->setPowerSave(!this->options.power_save); - break; case '~': ESP.restart(); break; - + case '@': + this->togglePowerSave(); + break; + case '-': b = this->options.brightness; while (*command++ == '-') @@ -843,7 +848,7 @@ class PatternController : public MessageReceiver { return; case 'V': - case 'g': + case 'G': case 'A': case 'W': case 'X': @@ -856,6 +861,16 @@ class PatternController : public MessageReceiver { return; } + case 'P': { + // Toggle power save + Action action = { + .key = command[0], + .arg = !this->power_save, + }; + this->broadcast_action(action); + break; + } + case 'R': this->setRole((ControllerRole)(arg >> 8)); return; @@ -872,7 +887,7 @@ class PatternController : public MessageReceiver { Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); Serial.println(F("l### - brightness")); - Serial.println("@ - power save mode"); + Serial.println("@ - toggle power saving mode"); Serial.println("U - begin auto-update"); Serial.println("O - offer an auto-update"); Serial.println("==== global actions ===="); @@ -990,6 +1005,11 @@ class PatternController : public MessageReceiver { ESP.restart(); return; + case '@': + Serial.print("Setting power save to %d\n"); + this->setPowerSave(action->arg); + return; + case 'W': Serial.println("Clearing WiFi connection."); strcpy(clientSSID, ""); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c05977ae4d..efb1d7876b 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -80,8 +80,10 @@ class DebugController { if (this->controller->isMaster()) { Serial.print("PRIMARY "); } - Serial.printf("role=%d\n", - this->controller->role); + Serial.printf("role=%d power_save=%d\n", + this->controller->role, + this->controller->power_save + ); // Dump WLED status char mode_name[50]; diff --git a/wled00/button.cpp b/wled00/button.cpp index 564b7f1161..1beb0d4359 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -8,8 +8,8 @@ #define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms #define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press #define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 -#define WLED_LONG_AP 3000 // how long button 0 needs to be held to activate WLED-AP - +#define WLED_LONG_AP 2000 // how long button 0 needs to be held to activate WLED-AP +#define WLED_LONG_POWER_SAVE 8000 // how long button 0 needs to be held to DEactivate power saving mode (toggle it) // SteveE: geez you can't put factory reset so close to "just turn on the AP" #define WLED_LONG_FACTORY_RESET 30000 // how long button 0 needs to be held to trigger a factory reset @@ -268,15 +268,22 @@ void handleButton() buttonWaitTime[b] = 0; if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released) + Serial.printf("A %ld", dur); if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds + Serial.printf(" B"); WLED_FS.format(); #ifdef WLED_ADD_EEPROM_SUPPORT clearEEPROM(); #endif doReboot = true; + } else if (dur > WLED_LONG_POWER_SAVE) { + Serial.printf(" C"); + usermods.handleButton(100); } else { + Serial.printf(" D"); WLED::instance().initAP(true); } + Serial.println(); } else if (!buttonLongPressed[b]) { //short press //NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set From fe7fe36729a1177b6281ce484efe411dc79ae5bf Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 02:14:17 -0700 Subject: [PATCH 167/263] Fix energy-related bugs --- usermods/Tubes/controller.h | 68 +++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a926d609a5..982fe14fed 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -20,8 +20,8 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; #define STATUS_UPDATE_PERIOD 2000 -#define MIN_COLOR_CHANGE_PHRASES 2 // 4 -#define MAX_COLOR_CHANGE_PHRASES 4 // 40 +#define MIN_COLOR_CHANGE_PHRASES 4 +#define MAX_COLOR_CHANGE_PHRASES 20 #define ROLE_EEPROM_LOCATION 2559 @@ -268,7 +268,8 @@ class PatternController : public MessageReceiver { this->patternOverrideTimer.stop(); transitionDelay = 8000; // Back to long transitions - this->set_wled_pattern(auto_mode, 128, 128); + uint8_t param = modeParameter(auto_mode); + this->set_wled_pattern(auto_mode, param, param); } } @@ -396,9 +397,9 @@ class PatternController : public MessageReceiver { void update_beat() { this->current_state.bpm = this->next_state.bpm = this->beats->bpm; this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) - if (this->current_state.bpm >= 125>>8) + if (this->current_state.bpm>>8 >= 125) this->energy = HighEnergy; - else if (this->current_state.bpm > 120>>8) + else if (this->current_state.bpm>>8 > 120) this->energy = MediumEnergy; else this->energy = Chill; @@ -452,6 +453,23 @@ class PatternController : public MessageReceiver { return this->current_state.pattern_id >= numInternalPatterns; } + uint8_t modeParameter(uint8_t mode) { + switch (this->energy) { + case Boring: + // Spice things up a bit + return 128; + + case Chill: + return 90; + + case MediumEnergy: + return 120; + + case HighEnergy: + return 140; + } + } + // For now, can't crossfade between internal and WLED patterns // If currently running an WLED pattern, only select from internal patterns. uint8_t get_valid_next_pattern() { @@ -473,7 +491,7 @@ class PatternController : public MessageReceiver { for (int i = 0; i < 10; i++) { pattern_id = get_valid_next_pattern(); def = gPatterns[pattern_id]; - if (def.control.energy < this->energy) + if (def.control.energy <= this->energy) break; } #ifdef IDENTIFY_STUCK_PATTERNS @@ -507,7 +525,11 @@ class PatternController : public MessageReceiver { uint16_t set_next_palette(uint16_t phrase) { // Don't select the built-in palettes this->next_state.palette_id = random8(6, gGradientPaletteCount); - return random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); + auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); + if (this->isBoring) { + phrases /= 2; + } + return phrases; } void load_effect(TubeState &tube_state) { @@ -536,20 +558,18 @@ class PatternController : public MessageReceiver { // Pick a random effect to add; boring patterns get better chance at having an effect. EffectDef def = gEffects[effect_num]; - Energy maxEnergy = this->energy; - if (this->isBoring) - maxEnergy = (Energy)((uint8_t)maxEnergy + 10); - if (def.control.energy > maxEnergy) + if (def.control.energy > this->energy) { def = gEffects[0]; + } this->next_state.effect_params = def.params; switch (def.control.duration) { - case ExtraShortDuration: return 2; - case ShortDuration: return 3; - case MediumDuration: return 6; - case LongDuration: return 10; - case ExtraLongDuration: return 20; + case ExtraShortDuration: return random(2,3); + case ShortDuration: return random(2,4); + case MediumDuration: return random(4,8); + case LongDuration: return random(6, 14); + case ExtraLongDuration: return random(10,20); } return 1; } @@ -571,7 +591,8 @@ class PatternController : public MessageReceiver { this->vstrips[this->next_vstrip]->load(background); this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; - set_wled_pattern(background.wled_fx_id, 128, 128); + uint8_t param = modeParameter(background.wled_fx_id); + set_wled_pattern(background.wled_fx_id, param, param); set_wled_palette(background.palette_id); } @@ -649,7 +670,7 @@ class PatternController : public MessageReceiver { EEPROM.write(ROLE_EEPROM_LOCATION, role); EEPROM.end(); delay(10); - ESP.restart(); + doReboot = true; } SyncMode randomSyncMode() { @@ -763,7 +784,7 @@ class PatternController : public MessageReceiver { this->setDebugging(!this->options.debugging); break; case '~': - ESP.restart(); + doReboot = true; break; case '@': this->togglePowerSave(); @@ -852,6 +873,7 @@ class PatternController : public MessageReceiver { case 'A': case 'W': case 'X': + case 'R': case 'M': { Action action = { .key = command[0], @@ -871,7 +893,7 @@ class PatternController : public MessageReceiver { break; } - case 'R': + case 'r': this->setRole((ControllerRole)(arg >> 8)); return; @@ -1002,7 +1024,11 @@ class PatternController : public MessageReceiver { return; case 'X': - ESP.restart(); + doReboot = true; + return; + + case 'R': + setRole((ControllerRole)(action->arg)); return; case '@': From 41f296126ff4ab8fb7f66b5a96357dca852f08e8 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 02:29:43 -0700 Subject: [PATCH 168/263] ignore compiler files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bb02e36ef2..789de0a9e7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ node_modules wled-update.sh esp01-update.sh /wled00/LittleFS -replace_fs.py \ No newline at end of file +replace_fs.py +wled00/wled00.ino.cpp From b6cb0542f4db6c6484fb21f22259006747d8047f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 03:55:35 -0700 Subject: [PATCH 169/263] More on-tube indicators --- usermods/Tubes/Tubes.h | 4 ++++ usermods/Tubes/controller.h | 45 +++++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 48f7e6027d..a1a780d15e 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -88,8 +88,12 @@ class TubesUsermod : public Usermod { master->handleOverlayDraw(); // When AP mode is on, make sure it's obvious + // Blink when there's a connected client if (apActive) { strip.setPixelColor(0, CRGB::Purple); + if (millis() % 10000 > 1000 && WiFi.softAPgetStationNum()) { + strip.setPixelColor(0, CRGB::Black); + } strip.setPixelColor(1, CRGB::Black); } } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 982fe14fed..c01b8ae81b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -100,6 +100,7 @@ class PatternController : public MessageReceiver { uint16_t wled_fader = 0; ControllerRole role; bool power_save = true; // Power save ALWAYS starts on. Some roles just ignore it + uint8_t flashColor = 0; AutoUpdater updater = AutoUpdater(); @@ -107,6 +108,7 @@ class PatternController : public MessageReceiver { Timer updateTimer; Timer paletteOverrideTimer; Timer patternOverrideTimer; + Timer flashTimer; #ifdef USELCD Lcd *lcd; @@ -156,7 +158,7 @@ class PatternController : public MessageReceiver { EEPROM.begin(2560); this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); if (this->role == 255) { - this->role = DefaultRole; + this->role = UnknownRole; } EEPROM.end(); Serial.printf("Role = %d", this->role); @@ -286,7 +288,7 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); // Detect manual overrides & update the current state to match. - if (this->paletteOverride && this->paletteOverrideTimer.ended()) { + if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { this->set_palette_override(0); } else if (segment.palette != this->current_state.palette_id) { this->set_palette_override(segment.palette); @@ -295,7 +297,7 @@ class PatternController : public MessageReceiver { uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; if (wled_mode < 10) wled_mode = DEFAULT_WLED_FX; - if (this->patternOverride && this->patternOverrideTimer.ended()) { + if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { this->set_pattern_override(0, wled_mode); } else if (segment.mode != wled_mode) { this->set_pattern_override(segment.mode, wled_mode); @@ -336,11 +338,12 @@ class PatternController : public MessageReceiver { this->wled_fader = 0xFFFF; } + uint16_t length = strip.getLengthTotal(); + // Crossfade between the custom pattern engine and WLED uint8_t fader = this->wled_fader >> 8; if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer - uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { CRGB c = this->led_strip->getPixelColor(i); if (fader > 0) { @@ -358,7 +361,6 @@ class PatternController : public MessageReceiver { // Only affects non-powered poles if (this->power_save && this->role < InstallationRole) { // Screen door effect to save power - uint16_t length = strip.getLengthTotal(); for (int i = 0; i < length; i++) { if (i % 2) { strip.setPixelColor(i, CRGB::Black); @@ -372,6 +374,19 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } + if (this->flashColor) { + if (flashTimer.ended()) + this->flashColor = 0; + else { + if (millis() % 4000 < 2000) { + auto chsv = CHSV(this->flashColor, 255, 255); + for (int i = 0; i < length; i++) { + strip.setPixelColor(i, CRGB(chsv)); + } + } + } + } + this->updater.handleOverlayDraw(); } @@ -462,11 +477,12 @@ class PatternController : public MessageReceiver { case Chill: return 90; - case MediumEnergy: - return 120; - case HighEnergy: return 140; + + default: + case MediumEnergy: + return 128; } } @@ -596,6 +612,10 @@ class PatternController : public MessageReceiver { set_wled_palette(background.palette_id); } + bool isUnderWledControl() { + return this->paletteOverride || this->patternOverride; + } + void set_wled_palette(uint8_t palette_id) { if (this->paletteOverride) palette_id = this->paletteOverride; @@ -873,6 +893,7 @@ class PatternController : public MessageReceiver { case 'A': case 'W': case 'X': + case 'F': case 'R': case 'M': { Action action = { @@ -1043,12 +1064,18 @@ class PatternController : public MessageReceiver { WiFi.disconnect(false, true); return; - case 'g': + case 'G': Serial.println("glitter!"); for (int i=0; i< 10; i++) addGlitter(); return; + case 'F': + Serial.println("flash!"); + this->flashTimer.start(20000); + this->flashColor = action->arg; + return; + case 'M': Serial.println("cancel manual mode"); this->paletteOverrideTimer.stop(); From 5bfa58ec5624fae251b7fd11c560142db48f41c5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 05:08:48 -0700 Subject: [PATCH 170/263] Invert power save button; smooth transition fades --- usermods/Tubes/Tubes.h | 4 ++++ usermods/Tubes/controller.h | 24 ++++++++++++++++++++---- usermods/Tubes/updater.h | 2 +- wled00/button.cpp | 18 +++++++----------- wled00/wled.h | 1 + 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index a1a780d15e..fa60f0356a 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -104,6 +104,10 @@ class TubesUsermod : public Usermod { this->controller.togglePowerSave(); return true; } + if (b == 101) { + this->controller.cancelOverrides(); + return true; + } return false; } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c01b8ae81b..8287a05e19 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -44,7 +44,7 @@ typedef enum ControllerRole : uint8_t { DefaultRole = 10, // Turn on in power saving mode CampRole = 50, // Turn on in non-power-saving mode InstallationRole = 100, // Disable power-saving mode completely - LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary + LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary MasterRole = 200 // Controls all the others } ControllerRole; @@ -239,6 +239,12 @@ class PatternController : public MessageReceiver { } } + void cancelOverrides() { + // Release the WLED overrides and take over control of the strip again. + this->paletteOverrideTimer.stop(); + this->patternOverrideTimer.stop(); + } + void set_palette_override(uint8_t value) { if (value == this->paletteOverride) return; @@ -336,6 +342,16 @@ class PatternController : public MessageReceiver { // In manual mode WLED is always active if (this->patternOverride) { this->wled_fader = 0xFFFF; + transition_mode_point = 0; + } else if (wled_fader == 0xFFFF) { + // When fading down... + // Wait until the transition has completely changed + // before switching to new mode + transition_mode_point = 0xFFFFU; + } else if (wled_fader == 0) { + // When fading up... + // Transition to new mode immediately + transition_mode_point = 0; } uint16_t length = strip.getLengthTotal(); @@ -888,6 +904,7 @@ class PatternController : public MessageReceiver { this->node->reset(arg >> 4); return; + case 'U': case 'V': case 'G': case 'A': @@ -941,7 +958,7 @@ class PatternController : public MessageReceiver { Serial.println("M - cancel manual pattern override"); return; - case 'U': + case 'u': this->updater.start(); return; @@ -1078,8 +1095,7 @@ class PatternController : public MessageReceiver { case 'M': Serial.println("cancel manual mode"); - this->paletteOverrideTimer.stop(); - this->patternOverrideTimer.stop(); + this->cancelOverrides(); break; case 'V': diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 2d0519fd5e..c2f727c578 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 4 +#define RELEASE_VERSION 5 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { diff --git a/wled00/button.cpp b/wled00/button.cpp index 1beb0d4359..147a3c773a 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -8,8 +8,8 @@ #define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms #define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press #define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 -#define WLED_LONG_AP 2000 // how long button 0 needs to be held to activate WLED-AP -#define WLED_LONG_POWER_SAVE 8000 // how long button 0 needs to be held to DEactivate power saving mode (toggle it) +#define WLED_LONG_POWER_SAVE 1000 // how long button 0 needs to be held to DEactivate power saving mode (toggle it) +#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP // SteveE: geez you can't put factory reset so close to "just turn on the AP" #define WLED_LONG_FACTORY_RESET 30000 // how long button 0 needs to be held to trigger a factory reset @@ -19,7 +19,7 @@ void shortPressAction(uint8_t b) { if (!macroButton[b]) { switch (b) { - case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break; + case 0: toggleOnOff(); usermods.handleButton(101); stateUpdated(CALL_MODE_BUTTON); break; case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break; } } else { @@ -267,21 +267,17 @@ void handleButton() bool doublePress = buttonWaitTime[b]; //did we have a short press before? buttonWaitTime[b] = 0; - if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released) - Serial.printf("A %ld", dur); + if (b == 0 && dur > WLED_LONG_POWER_SAVE) { // long press on button 0 (when released) if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds - Serial.printf(" B"); WLED_FS.format(); #ifdef WLED_ADD_EEPROM_SUPPORT clearEEPROM(); #endif doReboot = true; - } else if (dur > WLED_LONG_POWER_SAVE) { - Serial.printf(" C"); - usermods.handleButton(100); - } else { - Serial.printf(" D"); + } else if (dur > WLED_LONG_AP) { WLED::instance().initAP(true); + } else { + usermods.handleButton(100); } Serial.println(); } else if (!buttonLongPressed[b]) { //short press diff --git a/wled00/wled.h b/wled00/wled.h index c4262dea3b..4852ba3693 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -473,6 +473,7 @@ WLED_GLOBAL uint16_t transitionDelayTemp _INIT(transitionDelay); // actu WLED_GLOBAL unsigned long transitionStartTime; WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") +WLED_GLOBAL uint16_t transition_mode_point _INIT(0); // point at which a transition changes mode // nightlight WLED_GLOBAL bool nightlightActive _INIT(false); From e4afdd400547cc21ea89943dac4c0eb7ca996aab Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 24 Aug 2022 15:12:27 -0700 Subject: [PATCH 171/263] Ability to set BPM from any node, using double click --- usermods/Tubes/Tubes.h | 5 ++++ usermods/Tubes/controller.h | 43 +++++++++++++++++++++++++++-------- usermods/Tubes/global_state.h | 1 + usermods/Tubes/node.h | 10 ++++++-- wled00/button.cpp | 6 +++-- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index fa60f0356a..7930e6b80c 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -108,6 +108,11 @@ class TubesUsermod : public Usermod { this->controller.cancelOverrides(); return true; } + if (b == 102) { + this->controller.acknowledge(); + this->controller.request_new_bpm(); + return true; + } return false; } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8287a05e19..a94dd42aa8 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -425,6 +425,19 @@ class PatternController : public MessageReceiver { this->send_update(); } + void request_new_bpm(accum88 new_bpm = 0) { + // 0 = toggle 120 to 125 + if (new_bpm == 0) + new_bpm = this->current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; + + if (this->node->is_following()) { + // Send a request up to ROOT + this->broadcast_bpm(new_bpm); + } else { + this->set_tapped_bpm(new_bpm, 0); + } + } + void update_beat() { this->current_state.bpm = this->next_state.bpm = this->beats->bpm; this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) @@ -851,9 +864,7 @@ class PatternController : public MessageReceiver { Serial.println(F("nope")); return; } - this->beats->set_bpm(arg); - this->update_beat(); - this->send_update(); + request_new_bpm(arg); return; case 's': @@ -1008,13 +1019,18 @@ class PatternController : public MessageReceiver { this->node->sendCommand(COMMAND_UPGRADE, &this->updater.current_version, sizeof(this->updater.current_version)); } - virtual void onCommand(CommandId command, void *data) { + void broadcast_bpm(accum88 bpm) { + // Hacked in feature: request a new BPM + this->node->sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); + } + + virtual bool onCommand(CommandId command, void *data) { switch (command) { case COMMAND_INFO: Serial.printf(" \"%s\"\n", ((NodeInfo*)data)->message ); - return; + return true; case COMMAND_OPTIONS: memcpy(&this->options, data, sizeof(this->options)); @@ -1023,7 +1039,7 @@ class PatternController : public MessageReceiver { this->options.debugging, this->options.brightness ); - return; + return true; case COMMAND_STATE: { auto update_data = (TubeStates*)data; @@ -1039,19 +1055,28 @@ class PatternController : public MessageReceiver { this->load_palette(state); this->load_effect(state); this->beats->sync(state.bpm, state.beat_frame); - return; + return true; } case COMMAND_UPGRADE: this->updater.start((AutoUpdateOffer*)data); - return; + return true; case COMMAND_ACTION: this->onAction((Action*)data); - return; + return true; + + case COMMAND_BEATS: + // the master control ignores this request, it has its own + // beat measuring. + if (this->isMaster()) + return false; + this->set_tapped_bpm(*(accum88*)data, 0); + return true; } Serial.printf("UNKNOWN COMMAND %02X", command); + return false; } void onAction(Action* action) { diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index e4f8932a91..ba1b61d4bd 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -59,4 +59,5 @@ const static CommandId COMMAND_OPTIONS = 0x10; const static CommandId COMMAND_STATE = 0x20; const static CommandId COMMAND_ACTION = 0x30; const static CommandId COMMAND_INFO = 0x40; +const static CommandId COMMAND_BEATS = 0x50; const static CommandId COMMAND_UPGRADE = 0xE0; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index db9c652178..f40b2e8230 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -66,6 +66,8 @@ const char *command_name(CommandId command) { return "ACTION"; case COMMAND_INFO: return "INFO"; + case COMMAND_BEATS: + return "BEATS"; default: return "?COMMAND?"; } @@ -73,8 +75,9 @@ const char *command_name(CommandId command) { class MessageReceiver { public: - virtual void onCommand(CommandId command, void *data) { + virtual bool onCommand(CommandId command, void *data) { // Abstract: subclasses must define + return false; } }; @@ -266,11 +269,14 @@ class LightNode { strip.timebase = new_timebase; // Execute the command - this->receiver->onCommand( + auto valid = this->receiver->onCommand( message->command, &message->data ); Serial.println(); + + if (!valid) + return; } // Re-broadcast the message if appropriate diff --git a/wled00/button.cpp b/wled00/button.cpp index 147a3c773a..274aec6312 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -279,10 +279,12 @@ void handleButton() } else { usermods.handleButton(100); } - Serial.println(); } else if (!buttonLongPressed[b]) { //short press + if (b == 0 && doublePress) { + usermods.handleButton(102); + } //NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling - if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set + if (b > 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set shortPressAction(b); } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0) if (doublePress) { From b0ea57ede0df814fe806f1cfa9874a21b57eaa32 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 25 Aug 2022 03:47:16 -0700 Subject: [PATCH 172/263] better master control; small art role --- usermods/Tubes/controller.h | 17 ++++++++++++++++- usermods/Tubes/master.h | 22 +++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a94dd42aa8..404f19c409 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -44,7 +44,8 @@ typedef enum ControllerRole : uint8_t { DefaultRole = 10, // Turn on in power saving mode CampRole = 50, // Turn on in non-power-saving mode InstallationRole = 100, // Disable power-saving mode completely - LegacyRole = 190, // 1/2 the pixels, no "power saving" necessary + SmallArtRole = 120, // < 1/2 the pixels, scale the art + LegacyRole = 190, // LEGACY: 1/2 the pixels, no "power saving" necessary, no scaling MasterRole = 200 // Controls all the others } ControllerRole; @@ -390,6 +391,20 @@ class PatternController : public MessageReceiver { this->effects->draw(&strip); } + // Make the art half-size if it has a small number of pixels + if (this->role == MasterRole || this->role == SmallArtRole) { + int p = 0; + for (int i = 0; i < length; i++) { + CRGB c = strip.getPixelColor(i++); // i advances by 2 + CRGB c2 = strip.getPixelColor(i); + nblend(c, c2, 128); + if (this->role == MasterRole) { + nblend(c, CRGB::Black, 128); + } + strip.setPixelColor(p++, c); + } + } + if (this->flashColor) { if (flashTimer.ended()) this->flashColor = 0; diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 14fb9f8ecc..3b8b6c359f 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -11,13 +11,14 @@ #define BUTTON_PIN_2 71 // SKIP #define BUTTON_PIN_3 72 // SET COLOR #define BUTTON_PIN_4 23 // TAP -#define BUTTON_PIN_5 73 +#define BUTTON_PIN_5 25 // "NEXT!" class Master { public: uint8_t taps=0; Timer tapTimer; + Timer perTapTimer; uint16_t tapTime[16]; Background background; @@ -50,9 +51,13 @@ class Master { } } - if (this->taps && this->tapTimer.since_mark() > 10000) { + if (this->taps && this->perTapTimer.ended()) { + if (this->taps == 2) { + this->ok(); + } else { + this->fail(); + } this->taps = 0; - this->fail(); } } @@ -72,7 +77,7 @@ class Master { if (button == 0) return; - if (button == 1) { + if (button == 4) { Serial.println((char *)F("Skip >>")); this->controller->force_next(); this->ok(); @@ -113,6 +118,7 @@ class Master { if (!this->taps) { this->tapTimer.start(0); } + this->perTapTimer.start(1000); uint32_t time = this->tapTimer.since_mark(); this->tapTime[this->taps++] = time; @@ -141,6 +147,8 @@ class Master { this->controller->set_tapped_bpm(bpm); this->ok(); + } else if (this->taps >= 2) { + this->controller->set_tapped_bpm(this->controller->current_state.bpm, taps-1); } } @@ -151,16 +159,16 @@ class Master { this->displayPalette(this->background); } else { uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; - strip.setPixelColor(16 - beat_pos, CRGB::White); + strip.setPixelColor(15 - beat_pos, CRGB::White); } } void displayProgress(uint8_t progress) { for (int i = 0; i < 16; i++) { if (i < progress % 16) { - strip.setPixelColor(16 - i, CRGB(128,128,128)); + strip.setPixelColor(15 - i, CRGB(128,128,128)); } else { - strip.setPixelColor(16 - i, CRGB::Black); + strip.setPixelColor(15 - i, CRGB::Black); } } } From 1dc76116c76d08f5da51ac50d867c6a95ae6817d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 18:19:22 -0700 Subject: [PATCH 173/263] Fix updater mode --- usermods/Tubes/Tubes.h | 20 +++++++++++++++----- usermods/Tubes/controller.h | 32 ++++++++++++++++++++++++++++++-- usermods/Tubes/firmware.sh | 5 ++++- usermods/Tubes/updater.h | 15 ++++++++++++--- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 7930e6b80c..13b79354ad 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -91,7 +91,7 @@ class TubesUsermod : public Usermod { // Blink when there's a connected client if (apActive) { strip.setPixelColor(0, CRGB::Purple); - if (millis() % 10000 > 1000 && WiFi.softAPgetStationNum()) { + if (millis() % 4000 > 1000 && WiFi.softAPgetStationNum()) { strip.setPixelColor(0, CRGB::Black); } strip.setPixelColor(1, CRGB::Black); @@ -100,17 +100,27 @@ class TubesUsermod : public Usermod { bool handleButton(uint8_t b) { // Special code for handling the "power save" button - if (b == 100) { + if (b == 100) { // Press button 0 for WLED_LONG_POWER_SAVE ms this->controller.togglePowerSave(); return true; } - if (b == 101) { + if (b == 101) { // Short press button 0 (piggybacks with default) this->controller.cancelOverrides(); return true; } - if (b == 102) { + if (b == 102) { // Double-click button 0 this->controller.acknowledge(); - this->controller.request_new_bpm(); + if (this->controller.isSelecting()) { + if (apActive) { + // Reboot, to turn off WiFi + doReboot = true; + } else { + // Turn on WiFi for updating/comms + this->controller.updater.ready(); + } + } else { + this->controller.request_new_bpm(); + } return true; } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 404f19c409..c54a09db0a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -110,6 +110,7 @@ class PatternController : public MessageReceiver { Timer paletteOverrideTimer; Timer patternOverrideTimer; Timer flashTimer; + Timer selectTimer; #ifdef USELCD Lcd *lcd; @@ -150,7 +151,7 @@ class PatternController : public MessageReceiver { } bool isMaster() { - return this->role == MasterRole; + return this->role >= MasterRole; } void setup() @@ -246,6 +247,14 @@ class PatternController : public MessageReceiver { this->patternOverrideTimer.stop(); } + void enterSelectMode() { + this->selectTimer.start(20000); + } + + bool isSelecting() { + return !this->selectTimer.ended(); + } + void set_palette_override(uint8_t value) { if (value == this->paletteOverride) return; @@ -932,6 +941,9 @@ class PatternController : public MessageReceiver { case 'U': case 'V': + case '*': + case '(': + case ')': case 'G': case 'A': case 'W': @@ -977,7 +989,8 @@ class PatternController : public MessageReceiver { Serial.println("U - begin auto-update"); Serial.println("O - offer an auto-update"); Serial.println("==== global actions ===="); - Serial.println("A - turn on access point"); + Serial.println("* - enter select mode (double-click to Ready)"); + Serial.println("A - turn on access point (Ready to update)"); Serial.println("W - forget WiFi client"); Serial.println("X - restart"); Serial.println("V### - auto-upgrade to version"); @@ -1138,6 +1151,21 @@ class PatternController : public MessageReceiver { this->cancelOverrides(); break; + case 'S': + Serial.println("enter select mode"); + this->enterSelectMode(); + break; + + case '*': + case '(': + this->enterSelectMode(); + break; + + case ')': + this->updater.stop(); + WiFi.softAPdisconnect(true); + break; + case 'V': // Version check: prepare for update if (this->updater.current_version.version >= action->arg) diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 5d204756ee..133d647990 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -16,6 +16,10 @@ EOF } update_config() { + # No longer update configs + return; + + echo "Updating configuration via OTA" curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" >/dev/null curl -s http://$1/reset >/dev/null } @@ -25,7 +29,6 @@ update_firmware() { curl -s -F "update=@../../build_output/firmware/esp32_quinled_uno.bin" $1/update >/dev/null echo "Updated; wait..." sleep 5 - echo "Updating configuration via OTA" update_config $1 } diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index c2f727c578..937e3b012f 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 5 +#define RELEASE_VERSION 6 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { @@ -113,7 +113,10 @@ class AutoUpdater { log("ready for update - turning on updater AP"); strcpy(apSSID, "WLED-UPDATE"); strcpy(apPass, "update1234"); - WLED::instance().initAP(true); + auto tmp = apBehavior; + apBehavior = AP_BEHAVIOR_ALWAYS; + WLED::instance().initAP(); + apBehavior = tmp; this->status = Ready; } @@ -123,6 +126,8 @@ class AutoUpdater { strcpy(clientPass, _storedPass.c_str()); strcpy(apSSID, _storedAPSSID.c_str()); strcpy(apPass, _storedAPPass.c_str()); + WiFi.softAPdisconnect(true); + apActive = false; WiFi.disconnect(false, true); WLED::instance().enableWatchdog(); this->status = Idle; @@ -132,6 +137,9 @@ class AutoUpdater { CRGB c; switch (this->status) { case Ready: + // Once the updater connects, no need to show ready + if (WiFi.softAPgetStationNum()) + return; c = CRGB::Purple; break; @@ -156,7 +164,7 @@ class AutoUpdater { default: return; } - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 10; i++) { strip.setPixelColor(i, c); } } @@ -199,6 +207,7 @@ class AutoUpdater { case WL_CONNECTED: if (WiFi.SSID() != String(this->current_version.ssid)) { log("disconnecting from WiFi"); + WiFi.softAPdisconnect(true); WiFi.disconnect(false, true); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; return; From 94f65f8bea698b5e751114a4b598d1db9cdbf5fa Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 19:34:19 -0700 Subject: [PATCH 174/263] Sound reactive --- usermods/Tubes/controller.h | 9 +++++++ usermods/Tubes/debug.h | 3 +++ usermods/Tubes/particle.h | 7 ++++++ usermods/Tubes/sound.h | 47 +++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 usermods/Tubes/sound.h diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c54a09db0a..5f519f35b5 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -4,6 +4,7 @@ #include "wled.h" #include "FX.h" #include "updater.h" +#include "sound.h" #include "beats.h" @@ -104,6 +105,7 @@ class PatternController : public MessageReceiver { uint8_t flashColor = 0; AutoUpdater updater = AutoUpdater(); + Sounder sound = Sounder(); Timer graphicsTimer; Timer updateTimer; @@ -196,6 +198,8 @@ class PatternController : public MessageReceiver { this->set_wled_palette(0); // Default palette this->set_wled_pattern(0, 128, 128); // Default pattern + this->sound.setup(); + this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Patterns: ok"); } @@ -298,6 +302,9 @@ class PatternController : public MessageReceiver { // Update the mesh this->node->update(); + // Update sound meter + this->sound.update(); + // Update patterns to the beat this->update_beat(); @@ -383,6 +390,8 @@ class PatternController : public MessageReceiver { } } + this->sound.handleOverlayDraw(); + // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles if (this->power_save && this->role < InstallationRole) { diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index efb1d7876b..571b07b0e3 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -80,6 +80,9 @@ class DebugController { if (this->controller->isMaster()) { Serial.print("PRIMARY "); } + if (this->controller->sound.active) { + Serial.print("SOUND "); + } Serial.printf("role=%d power_save=%d\n", this->controller->role, this->controller->power_save diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index fb0ba887ee..da0d026957 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -10,6 +10,7 @@ class Particle; typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); +uint8_t particleVolume = 64; // Default mic average is around 64 during music extern void drawPoint(Particle *particle, WS2812FX* leds); extern void drawFlash(Particle *particle, WS2812FX* leds); @@ -48,6 +49,9 @@ class Particle { void update(BeatFrame_24_8 frame) { + // Particles get brighter with the beat + this->brightness = (scale8(particleVolume, 80) + 170) << 8; + this->age = frame - this->born; this->position = this->udelta16(this->position, this->velocity); this->velocity = this->delta16(this->velocity, this->gravity); @@ -204,6 +208,9 @@ void drawPoint(Particle *particle, WS2812FX* leds) { } void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { + // Bump up the radius with any beats + radius += scale8(particleVolume, 8) - 2; + auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; diff --git a/usermods/Tubes/sound.h b/usermods/Tubes/sound.h new file mode 100644 index 0000000000..79a559911a --- /dev/null +++ b/usermods/Tubes/sound.h @@ -0,0 +1,47 @@ + +#include "wled.h" +#include "fcn_declare.h" +#include "particle.h" + + +class Sounder { + public: + bool active = false; + uint8_t volume; + + void setup() { + this->active = true; + } + + void update() { + if (!this->active) { + particleVolume = 128; // Average volume + return; + } + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + this->active = false; + return; + } + + float volumeSmth = *(float*) um_data->u_data[0]; + this->volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. + particleVolume = this->volume; + } + + void handleOverlayDraw() { + if (!this->active) + return; + + int len = scale8(this->volume, 32); + for (int i = 0; i < len; i++) { + strip.setPixelColor(i, CRGB::White); + } + } + + static float mapf(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; + } + +}; From c20d6020c8d8451af4da542addf8f2983bb426ce Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 19:59:53 -0700 Subject: [PATCH 175/263] better sound overlay --- usermods/Tubes/controller.h | 20 +++++++++++++++----- usermods/Tubes/effects.h | 1 + usermods/Tubes/particle.h | 12 +++++++----- usermods/Tubes/sound.h | 17 ++++++++++++----- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 5f519f35b5..8bb933d5ca 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -978,6 +978,16 @@ class PatternController : public MessageReceiver { break; } + case 'O': { + // Toggle power save + Action action = { + .key = command[0], + .arg = !this->sound.overlay + }; + this->broadcast_action(action); + break; + } + case 'r': this->setRole((ControllerRole)(arg >> 8)); return; @@ -996,7 +1006,7 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); Serial.println("@ - toggle power saving mode"); Serial.println("U - begin auto-update"); - Serial.println("O - offer an auto-update"); + Serial.println("O - toggle all sound overlays"); Serial.println("==== global actions ===="); Serial.println("* - enter select mode (double-click to Ready)"); Serial.println("A - turn on access point (Ready to update)"); @@ -1010,10 +1020,6 @@ class PatternController : public MessageReceiver { this->updater.start(); return; - case 'O': - broadcast_autoupdate(); - return; - case 0: // Empty command return; @@ -1123,6 +1129,10 @@ class PatternController : public MessageReceiver { WLED::instance().initAP(true); return; + case 'O': + this->sound.overlay = (action->arg != 0); + return; + case 'X': doReboot = true; return; diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index bdb59fe957..22881c771b 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -151,6 +151,7 @@ static const EffectDef gEffects[] = { {{Bubble, Brighten}, {MediumDuration, Chill}}, {{Bubble, Brighten}, {MediumDuration, Chill}}, {{Bubble, Darken}, {MediumDuration, Chill}}, + {{Bubble, Invert}, {MediumDuration, Chill}}, {{Bubble, Invert}, {MediumDuration, Chill}}, {{Glitter, Darken, Eighth, 120}, {MediumDuration, MediumEnergy}}, diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index da0d026957..2db9189e61 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -7,10 +7,12 @@ #define MAX_PARTICLES 80 #undef PARTICLE_PALETTES +#define DEFAULT_PARTICLE_VOLUME 64 // Default mic average is around 64 during music + class Particle; typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); -uint8_t particleVolume = 64; // Default mic average is around 64 during music +uint8_t particleVolume = DEFAULT_PARTICLE_VOLUME; extern void drawPoint(Particle *particle, WS2812FX* leds); extern void drawFlash(Particle *particle, WS2812FX* leds); @@ -208,9 +210,6 @@ void drawPoint(Particle *particle, WS2812FX* leds) { } void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { - // Bump up the radius with any beats - radius += scale8(particleVolume, 8) - 2; - auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; @@ -242,7 +241,10 @@ void drawBeatbox(Particle *particle, WS2812FX* leds) { uint16_t age_frac = particle->age_frac16(particle->age); CRGB c = particle->color_at(age_frac); uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); - uint8_t radius = 5; + uint8_t radius = 3; + + // Bump up the radius with any beats + radius += scale8(particleVolume, 8); drawRadius(particle, leds, pos, radius, c, false); } diff --git a/usermods/Tubes/sound.h b/usermods/Tubes/sound.h index 79a559911a..02280170c5 100644 --- a/usermods/Tubes/sound.h +++ b/usermods/Tubes/sound.h @@ -6,26 +6,27 @@ class Sounder { public: - bool active = false; + bool active = true; + bool overlay = false; uint8_t volume; void setup() { - this->active = true; } void update() { if (!this->active) { - particleVolume = 128; // Average volume + particleVolume = DEFAULT_PARTICLE_VOLUME; // Average volume return; } um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { this->active = false; + this->overlay = false; return; } - float volumeSmth = *(float*) um_data->u_data[0]; + float volumeSmth = *(float*)um_data->u_data[0]; this->volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. particleVolume = this->volume; } @@ -33,10 +34,16 @@ class Sounder { void handleOverlayDraw() { if (!this->active) return; + if (!this->overlay) + return; int len = scale8(this->volume, 32); + + Segment& segment = strip.getMainSegment(); + for (int i = 0; i < len; i++) { - strip.setPixelColor(i, CRGB::White); + uint32_t color = segment.color_from_palette(i, true, true, 255, 192); + strip.setPixelColor(i, color); } } From 71cdf412c77e062d192372006c2ad36b7fb4cbc1 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 20:09:55 -0700 Subject: [PATCH 176/263] Better selection mode --- usermods/Tubes/Tubes.h | 11 ++++------- usermods/Tubes/controller.h | 39 ++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 13b79354ad..773f259e08 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -111,13 +111,10 @@ class TubesUsermod : public Usermod { if (b == 102) { // Double-click button 0 this->controller.acknowledge(); if (this->controller.isSelecting()) { - if (apActive) { - // Reboot, to turn off WiFi - doReboot = true; - } else { - // Turn on WiFi for updating/comms - this->controller.updater.ready(); - } + if (this->controller.isSelected()) + this->controller.deselect(); + else + this->controller.select(); } else { this->controller.request_new_bpm(); } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8bb933d5ca..35e745ad32 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -259,6 +259,23 @@ class PatternController : public MessageReceiver { return !this->selectTimer.ended(); } + bool isSelected() { + return this->updater.status == Ready; + } + + void select(bool selected = true) { + if (selected) + this->updater.ready(); + else { + this->updater.stop(); + WiFi.softAPdisconnect(true); + } + } + + void deselect() { + select(false); + } + void set_palette_override(uint8_t value) { if (value == this->paletteOverride) return; @@ -1134,10 +1151,14 @@ class PatternController : public MessageReceiver { return; case 'X': + if (!this->isSelected()) + return; doReboot = true; return; case 'R': + if (!this->isSelected()) + return; setRole((ControllerRole)(action->arg)); return; @@ -1170,19 +1191,15 @@ class PatternController : public MessageReceiver { this->cancelOverrides(); break; - case 'S': - Serial.println("enter select mode"); - this->enterSelectMode(); - break; - case '*': case '(': + Serial.println("enter select mode"); this->enterSelectMode(); break; case ')': - this->updater.stop(); - WiFi.softAPdisconnect(true); + Serial.println("exit select mode"); + this->deselect(); break; case 'V': @@ -1190,13 +1207,13 @@ class PatternController : public MessageReceiver { if (this->updater.current_version.version >= action->arg) break; - this->updater.ready(); + this->select(); break; case 'U': - if (this->updater.status == Ready) { - this->updater.start(); - } + if (!this->isSelected()) + return; + this->updater.start(); break; } From 90eace2ca8f798ba42e29089b0f02d564590b4c0 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 20:10:24 -0700 Subject: [PATCH 177/263] Playa-ready verison = 10 --- usermods/Tubes/updater.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 937e3b012f..cbf95d2b83 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 6 +#define RELEASE_VERSION 10 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { From 868211cd0feef20cb9dcd3686847c4100bd31d55 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 27 Aug 2022 20:16:55 -0700 Subject: [PATCH 178/263] Draw the overlay -after- power save --- usermods/Tubes/controller.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 35e745ad32..bb8997370c 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -407,8 +407,6 @@ class PatternController : public MessageReceiver { } } - this->sound.handleOverlayDraw(); - // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles if (this->power_save && this->role < InstallationRole) { @@ -420,6 +418,8 @@ class PatternController : public MessageReceiver { } } + this->sound.handleOverlayDraw(); + // Draw effects layers over whatever WLED is doing. // But not in manual (WLED) mode if (!this->patternOverride) { From ff471978667bcaafc528950bcadf520c2606c309 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Aug 2022 01:08:54 -0700 Subject: [PATCH 179/263] Lower power requirements? --- usermods/Tubes/controller.h | 32 ++++++++++++++++---------------- usermods/Tubes/firmware.sh | 6 +++--- wled00/FX_fcn.cpp | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index bb8997370c..a16e66b0b1 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,8 +15,8 @@ #include "global_state.h" #include "node.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 100; -const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 128; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 80; +const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; #define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE #define STATUS_UPDATE_PERIOD 2000 @@ -165,25 +165,25 @@ class PatternController : public MessageReceiver { this->role = UnknownRole; } EEPROM.end(); - Serial.printf("Role = %d", this->role); + Serial.printf("Role = %d\n", this->role); this->power_save = (this->role < CampRole); - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - this->options.debugging = false; - switch (role) { - case MasterRole: - this->node->reset(4050); // MASTER ID - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; - break; + if (this->role <= CampRole) + strip.ablMilliampsMax = 700; // Really limit for batteries + else if (this->role <= InstallationRole) + strip.ablMilliampsMax = 1000; + else + strip.ablMilliampsMax = 1400; - case LegacyRole: + if (this->role >= MasterRole) { + this->node->reset(3850 + this->role); // MASTER ID + this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; + } else if (this->role >= LegacyRole) { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - break; - - default: + } else { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - break; } + this->options.debugging = false; this->load_options(this->options); #ifdef USELCD @@ -201,7 +201,7 @@ class PatternController : public MessageReceiver { this->sound.setup(); this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to - Serial.println("Patterns: ok"); + Serial.println("Controller: ok"); } void do_pattern_changes() { diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 133d647990..b8b5d81698 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -16,8 +16,8 @@ EOF } update_config() { - # No longer update configs - return; + # No longer update configs? comment this + # return; echo "Updating configuration via OTA" curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" >/dev/null @@ -56,7 +56,7 @@ update_one() { } update_batch() { - airport -s | grep WLED | cut -c23-32 | while read line + airport -s | grep WLED | cut -c10-32 | while read line do if [ "$line" == "WLED-AP" ]; then update_one 4.3.2.1 "$line" "wled1234" diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 794676f9e8..73a40be049 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1043,7 +1043,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i) //I am NOT to be held liable for burned down garages! //fine tune power estimation constants for your setup -#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) +#define MA_FOR_ESP 300 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) //you can set it to 0 if the ESP is powered by USB and the LEDs by external void WS2812FX::estimateCurrentAndLimitBri() { From 06a7698a96e640b4ddc293c08c52585248b86f8d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Aug 2022 09:18:03 -0700 Subject: [PATCH 180/263] misc --- usermods/Tubes/controller.h | 63 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a16e66b0b1..03d2c08a88 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -22,7 +22,7 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; #define STATUS_UPDATE_PERIOD 2000 #define MIN_COLOR_CHANGE_PHRASES 4 -#define MAX_COLOR_CHANGE_PHRASES 20 +#define MAX_COLOR_CHANGE_PHRASES 10 #define ROLE_EEPROM_LOCATION 2559 @@ -97,6 +97,7 @@ class PatternController : public MessageReceiver { uint8_t num_leds; VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; + bool canOverride = false; uint8_t paletteOverride = 0; uint8_t patternOverride = 0; uint16_t wled_fader = 0; @@ -277,6 +278,8 @@ class PatternController : public MessageReceiver { } void set_palette_override(uint8_t value) { + if (!this->canOverride) + return; if (value == this->paletteOverride) return; @@ -292,6 +295,8 @@ class PatternController : public MessageReceiver { } void set_pattern_override(uint8_t value, uint8_t auto_mode) { + if (!this->canOverride) + return; if (value == DEFAULT_WLED_FX && !this->patternOverride) return; if (value == this->patternOverride) @@ -328,19 +333,21 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); // Detect manual overrides & update the current state to match. - if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { - this->set_palette_override(0); - } else if (segment.palette != this->current_state.palette_id) { - this->set_palette_override(segment.palette); - } - - uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; - if (wled_mode < 10) - wled_mode = DEFAULT_WLED_FX; - if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { - this->set_pattern_override(0, wled_mode); - } else if (segment.mode != wled_mode) { - this->set_pattern_override(segment.mode, wled_mode); + if (this->canOverride) { + if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { + this->set_palette_override(0); + } else if (segment.palette != this->current_state.palette_id) { + this->set_palette_override(segment.palette); + } + + uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; + if (wled_mode < 10) + wled_mode = DEFAULT_WLED_FX; + if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { + this->set_pattern_override(0, wled_mode); + } else if (segment.mode != wled_mode) { + this->set_pattern_override(segment.mode, wled_mode); + } } do_pattern_changes(); @@ -352,7 +359,7 @@ class PatternController : public MessageReceiver { // Update current status if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!this->node->is_following() || random(0, 5) == 0) { + if (!this->node->is_following() || random(0, 4) == 0) { this->send_update(); } } @@ -427,13 +434,13 @@ class PatternController : public MessageReceiver { } // Make the art half-size if it has a small number of pixels - if (this->role == MasterRole || this->role == SmallArtRole) { + if (this->role >= MasterRole || this->role == SmallArtRole) { int p = 0; for (int i = 0; i < length; i++) { CRGB c = strip.getPixelColor(i++); // i advances by 2 CRGB c2 = strip.getPixelColor(i); nblend(c, c2, 128); - if (this->role == MasterRole) { + if (this->role >= MasterRole) { nblend(c, CRGB::Black, 128); } strip.setPixelColor(p++, c); @@ -491,12 +498,14 @@ class PatternController : public MessageReceiver { void update_beat() { this->current_state.bpm = this->next_state.bpm = this->beats->bpm; this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) - if (this->current_state.bpm>>8 >= 125) + if (this->current_state.bpm>>8 <= 118) // Hip hop / ghettofunk + this->energy = MediumEnergy; + else if (this->current_state.bpm>>8 >= 125) // House & breaks this->energy = HighEnergy; - else if (this->current_state.bpm>>8 > 120) + else if (this->current_state.bpm>>8 > 120) // Tech house this->energy = MediumEnergy; else - this->energy = Chill; + this->energy = Chill; // Deep house } void send_update() { @@ -600,8 +609,8 @@ class PatternController : public MessageReceiver { case ExtraShortDuration: return random8(2, 6); case ShortDuration: return random8(5,15); case MediumDuration: return random8(15,25); - case LongDuration: return random8(35,45); - case ExtraLongDuration: return random8(70, 100); + case LongDuration: return random8(20,40); + case ExtraLongDuration: return random8(25, 60); } return 5; } @@ -621,6 +630,8 @@ class PatternController : public MessageReceiver { // Don't select the built-in palettes this->next_state.palette_id = random8(6, gGradientPaletteCount); auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); + + // Change color more often in boring patterns if (this->isBoring) { phrases /= 2; } @@ -660,11 +671,11 @@ class PatternController : public MessageReceiver { this->next_state.effect_params = def.params; switch (def.control.duration) { - case ExtraShortDuration: return random(2,3); + case ExtraShortDuration: return random(1,3); case ShortDuration: return random(2,4); - case MediumDuration: return random(4,8); - case LongDuration: return random(6, 14); - case ExtraLongDuration: return random(10,20); + case MediumDuration: return random(4,7); + case LongDuration: return random(8, 11); + case ExtraLongDuration: return random(10,15); } return 1; } From 8d02979fc41ab22b183481bf160992efa726a7c5 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Aug 2022 09:22:52 -0700 Subject: [PATCH 181/263] manual mode protection --- usermods/Tubes/controller.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 03d2c08a88..2c9324c411 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -332,6 +332,10 @@ class PatternController : public MessageReceiver { Segment& segment = strip.getMainSegment(); + // You can only go into manual control after enabling the wifi + if (apActive && this->updater.status != Ready) + this->canOverride = true; + // Detect manual overrides & update the current state to match. if (this->canOverride) { if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { From 2c68877f31f2816b9d5118397bf393c728cf480b Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 1 Sep 2022 20:12:54 -0700 Subject: [PATCH 182/263] better taps? and art car brightness --- usermods/Tubes/controller.h | 6 +++++- usermods/Tubes/master.h | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 2c9324c411..b8bc645017 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,8 +15,9 @@ #include "global_state.h" #include "node.h" -const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 80; +const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; +const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; #define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE #define STATUS_UPDATE_PERIOD 2000 @@ -27,6 +28,7 @@ const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; #define ROLE_EEPROM_LOCATION 2559 #define IDENTIFY_STUCK_PATTERNS +#define IDENTIFY_STUCK_PALETTES typedef struct { bool debugging; @@ -181,6 +183,8 @@ class PatternController : public MessageReceiver { this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; } else if (this->role >= LegacyRole) { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + } else if (this->role == InstallationRole) { + this->options.brightness = DEFAULT_TANK_BRIGHTNESS; } else { this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; } diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 3b8b6c359f..b341b73481 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -114,11 +114,10 @@ class Master { } void tap() { - Serial.println((char *)F("tap")); if (!this->taps) { this->tapTimer.start(0); } - this->perTapTimer.start(1000); + this->perTapTimer.start(1500); uint32_t time = this->tapTimer.since_mark(); this->tapTime[this->taps++] = time; @@ -127,13 +126,22 @@ class Master { if (this->taps > 4) { // Can study this later to make BPM detection better - bpm = 60000*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat + // Should be 60000; fudge a bit to adjust to real-world timings + bpm = 60220*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat if (bpm < 70*256) bpm *= 2; else if (bpm > 140*256) bpm /= 2; } - + + Serial.printf("tap %d: ", this->taps); + Serial.print(bpm >> 8); + uint8_t f = scale8(100, bpm & 0xFF); + Serial.print("."); + if (f < 10) + Serial.print("0"); + Serial.println(f); + if (this->taps == 16) { Serial.println("OK! taps"); this->taps = 0; From ad760319959b5ca6b5feaf908222f97015f6d25e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 7 Sep 2022 13:55:12 -0700 Subject: [PATCH 183/263] Fix after merge --- wled00/FX.cpp | 49 --------------------------------- wled00/FX.h | 64 -------------------------------------------- wled00/FX_fcn.cpp | 3 ++- wled00/data/index.js | 3 ++- 4 files changed, 4 insertions(+), 115 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 453d1dc3f1..fbe72a89eb 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7516,53 +7516,4 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_DJLIGHT, &mode_DJLight, _data_FX_MODE_DJLIGHT); addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); -<<<<<<< HEAD } - - - - -#define MAX_VIRTUAL_LEDS 150 -uint8_t noise[MAX_VIRTUAL_LEDS]; - -void fillnoise8(uint32_t frame, uint8_t num_leds) { - uint16_t scale = 17; - uint8_t dataSmoothing = 240; - - for (int i = 0; i < num_leds; i++) { - uint8_t data = inoise8(i * scale, frame>>2); - - // The range of the inoise8 function is roughly 16-238. - // These two operations expand those values out to roughly 0..255 - data = qsub8(data,16); - data = qadd8(data,scale8(data,39)); - - uint8_t olddata = noise[i]; - uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); - noise[i] = newdata; - } -} - -uint16_t WS2812FX::mode_tubes_moise(void) { - uint16_t pixelLen = SEGLEN > MAX_VIRTUAL_LEDS ? MAX_VIRTUAL_LEDS : SEGLEN; - // uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 - // if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - // uint32_t* pixels = reinterpret_cast(SEGENV.data); - uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); - uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; - - // generate noise data - fillnoise8(now>>4, pixelLen); - - uint16_t offset = 0; - for (int i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(noise[i], true, PALETTE_SOLID_WRAP, 0)); - } - - return FRAMETIME; -} -======= - //addEffect(FX_MODE_CUSTOMEFFECT, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT); //WLEDSR Custom Effects -#endif // USERMOD_AUDIOREACTIVE -} ->>>>>>> 0a7756d3 (fix post merge) diff --git a/wled00/FX.h b/wled00/FX.h index 871af4e751..e39d9aef2d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -275,7 +275,6 @@ #define FX_MODE_2DMETABALLS 142 // non audio #define FX_MODE_2DPULSER 143 // non audio #define FX_MODE_2DDRIFT 144 // non audio -<<<<<<< HEAD #define FX_MODE_2DWAVERLY 145 // audio enhanced #define FX_MODE_2DSWIRL 146 // audio enhanced #define FX_MODE_2DAKEMI 147 // audio enhanced @@ -309,69 +308,6 @@ #define FX_MODE_ROCKTAVES 174 // audio enhanced #define MODE_COUNT 175 -======= -#endif -#ifndef WLED_DISABLE_AUDIO - #ifndef WLED_DISABLE_2D - #define FX_MODE_2DWAVERLY 145 // audio enhanced - #define FX_MODE_2DSWIRL 146 // audio enhanced - #define FX_MODE_2DAKEMI 147 // audio enhanced - // 148 & 149 reserved - #endif - #define FX_MODE_PIXELWAVE 150 // audio enhanced - #define FX_MODE_JUGGLES 151 // audio enhanced - #define FX_MODE_MATRIPIX 152 // audio enhanced - #define FX_MODE_GRAVIMETER 153 // audio enhanced - #define FX_MODE_PLASMOID 154 // audio enhanced - #define FX_MODE_PUDDLES 155 // audio enhanced - #define FX_MODE_MIDNOISE 156 // audio enhanced - #define FX_MODE_NOISEMETER 157 // audio enhanced - #define FX_MODE_NOISEFIRE 158 // audio enhanced - #define FX_MODE_PUDDLEPEAK 159 // audio enhanced - #define FX_MODE_RIPPLEPEAK 160 // audio enhanced - #define FX_MODE_GRAVCENTER 161 // audio enhanced - #define FX_MODE_GRAVCENTRIC 162 // audio enhanced -#endif - -#ifndef USERMOD_AUDIOREACTIVE - - #ifndef WLED_DISABLE_AUDIO - #define MODE_COUNT 163 - #else - #ifndef WLED_DISABLE_2D - #define MODE_COUNT 145 - #else - #define MODE_COUNT 118 - #endif - #endif - -#else - - #ifdef WLED_DISABLE_AUDIO - #error Incompatible options: WLED_DISABLE_AUDIO and USERMOD_AUDIOREACTIVE - #endif - #ifdef WLED_DISABLE_2D - #error AUDIOREACTIVE usermod requires 2D support. - #endif - #define FX_MODE_2DGEQ 148 - #define FX_MODE_2DFUNKYPLANK 149 - #define FX_MODE_PIXELS 163 - #define FX_MODE_FREQWAVE 164 - #define FX_MODE_FREQMATRIX 165 - #define FX_MODE_WATERFALL 166 - #define FX_MODE_FREQPIXELS 167 - #define FX_MODE_BINMAP 168 - #define FX_MODE_NOISEMOVE 169 - #define FX_MODE_FREQMAP 170 - #define FX_MODE_GRAVFREQ 171 - #define FX_MODE_DJLIGHT 172 - #define FX_MODE_BLURZ 173 - #define FX_MODE_ROCKTAVES 174 - //#define FX_MODE_CUSTOMEFFECT 175 //WLEDSR Custom Effects - - #define MODE_COUNT 175 -#endif ->>>>>>> d4cc6df2 (Handle manual override of colors) typedef enum mapping1D2D { M12_Pixels = 0, diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 73a40be049..9e7cc070c7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -203,6 +203,7 @@ void Segment::setUpLeds() { else #endif leds = (CRGB*)malloc(sizeof(CRGB)*length()); + } } CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { @@ -1043,7 +1044,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i) //I am NOT to be held liable for burned down garages! //fine tune power estimation constants for your setup -#define MA_FOR_ESP 300 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) +#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) //you can set it to 0 if the ESP is powered by USB and the LEDs by external void WS2812FX::estimateCurrentAndLimitBri() { diff --git a/wled00/data/index.js b/wled00/data/index.js index bda92ee073..27cd1dc8ab 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2105,7 +2105,8 @@ function setFX(ind = null) d.querySelector(`#fxlist input[name="fx"][value="${ind}"]`).checked = true; } - var obj = {"seg": {"fx": parseInt(ind),"fxdef":1}}; // fxdef sets effect parameters to default values, TODO add client setting + // STEVEE: Removed "fxdef:1" + var obj = {"seg": {"fx": parseInt(ind)}}; // fxdef sets effect parameters to default values, TODO add client setting requestJson(obj); } From 9c7f1694c379ebd2ac8d84f157b56aee0643bf77 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 7 Oct 2022 11:28:13 -0700 Subject: [PATCH 184/263] Permanently store power_save option --- usermods/Tubes/controller.h | 38 ++++++++++++++++++++++++++++++++++++- usermods/Tubes/updater.h | 2 +- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b8bc645017..0382c04b9f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -26,6 +26,7 @@ const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; #define MAX_COLOR_CHANGE_PHRASES 10 #define ROLE_EEPROM_LOCATION 2559 +#define BOOT_OPTIONS_EEPROM_LOCATION 2557 #define IDENTIFY_STUCK_PATTERNS #define IDENTIFY_STUCK_PALETTES @@ -52,6 +53,14 @@ typedef enum ControllerRole : uint8_t { MasterRole = 200 // Controls all the others } ControllerRole; +typedef struct BootOptions { + unsigned int default_power_save:2; +} BootOptions; + +#define BOOT_OPTION_POWER_SAVE_DEFAULT 0 +#define BOOT_OPTION_POWER_SAVE_OFF 1 +#define BOOT_OPTION_POWER_SAVE_ON 2 + typedef struct { char key; uint8_t arg; @@ -170,7 +179,21 @@ class PatternController : public MessageReceiver { EEPROM.end(); Serial.printf("Role = %d\n", this->role); - this->power_save = (this->role < CampRole); + auto b = EEPROM.read(BOOT_OPTIONS_EEPROM_LOCATION); + Serial.printf("EEPROM read: %d\n", b); + BootOptions* boot = (BootOptions*)&b; + switch (boot->default_power_save) { + case BOOT_OPTION_POWER_SAVE_OFF: + this->power_save = 0; + break; + case BOOT_OPTION_POWER_SAVE_ON: + this->power_save = 1; + break; + default: + this->power_save = (this->role < CampRole); + break; + } + if (this->role <= CampRole) strip.ablMilliampsMax = 700; // Really limit for batteries else if (this->role <= InstallationRole) @@ -779,6 +802,18 @@ class PatternController : public MessageReceiver { void setPowerSave(bool power_save) { Serial.printf("power_save: %d\n", power_save); this->power_save = power_save; + + // Remember this setting on the next boot + EEPROM.begin(2560); + auto b = EEPROM.read(BOOT_OPTIONS_EEPROM_LOCATION); + BootOptions* boot = (BootOptions*)&b; + if (power_save) + boot->default_power_save = BOOT_OPTION_POWER_SAVE_ON; + else + boot->default_power_save = BOOT_OPTION_POWER_SAVE_OFF; + EEPROM.write(BOOT_OPTIONS_EEPROM_LOCATION, b); // Reset all boot options + Serial.printf("wrote: %d\n", b); + EEPROM.end(); } void setRole(ControllerRole role) { @@ -786,6 +821,7 @@ class PatternController : public MessageReceiver { Serial.printf("Role = %d", role); EEPROM.begin(2560); EEPROM.write(ROLE_EEPROM_LOCATION, role); + EEPROM.write(BOOT_OPTIONS_EEPROM_LOCATION, 0); // Reset all boot options EEPROM.end(); delay(10); doReboot = true; diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index cbf95d2b83..43923a19e4 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 10 +#define RELEASE_VERSION 11 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { From 3fd6ec60f2fcfb4f852fc97fd09e6800fcffdf7f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 21 Jul 2023 13:09:52 -0700 Subject: [PATCH 185/263] Update to latest version of WLED --- usermods/Tubes/controller.h | 3 ++- usermods/Tubes/default_config.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 0382c04b9f..294830df2b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1051,7 +1051,7 @@ class PatternController : public MessageReceiver { } case 'O': { - // Toggle power save + // Toggle sound overlay Action action = { .key = command[0], .arg = !this->sound.overlay @@ -1078,6 +1078,7 @@ class PatternController : public MessageReceiver { Serial.println(F("l### - brightness")); Serial.println("@ - toggle power saving mode"); Serial.println("U - begin auto-update"); + Serial.println("P - toggle all power saves"); Serial.println("O - toggle all sound overlays"); Serial.println("==== global actions ===="); Serial.println("* - enter select mode (double-click to Ready)"); diff --git a/usermods/Tubes/default_config.json b/usermods/Tubes/default_config.json index 55c340b5f7..5676f56cf3 100644 --- a/usermods/Tubes/default_config.json +++ b/usermods/Tubes/default_config.json @@ -1 +1 @@ -{"rev":[1,0],"vid":2208180,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"Tube","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":1000,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0}]},"com":[],"btn":{"max":4,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8},"tr":{"mode":true,"dur":80,"pal":1},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"twice":false,"grp":1}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"addr":1,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0]},"hue":{"en":false,"id":1,"iv":25,"recv":{"on":true,"bri":true,"col":true},"ip":[0,0,0,0]},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":false,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1,-1,-1]},"cfg":{"squelch":10,"gain":60,"AGC":0},"dynamics":{"Limiter":true,"Rise":80,"Fall":1400},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file +{"rev":[1,0],"vid":2307130,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"Tube","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":1000,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0,"freq":0}]},"com":[],"btn":{"max":4,"pull":true,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8,"val":2.8},"tr":{"mode":true,"dur":80,"pal":1,"rpc":5},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"grp":1,"ret":0}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"e131prio":0,"addr":1,"dss":0,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0],"p":0},"hue":{"en":false,"id":1,"iv":25,"recv":{"on":true,"bri":true,"col":true},"ip":[0,0,0,0]},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"remote":{"remote_enabled":false,"linked_remote":""},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":false,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1]},"config":{"squelch":10,"gain":60,"AGC":1},"dynamics":{"limiter":true,"rise":80,"fall":1400},"frequency":{"scale":3},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file From 505ed5ac1a208213c4c5413432364968ad52c412 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 12 Aug 2023 23:53:01 -0400 Subject: [PATCH 186/263] Fix tasks --- .vscode/tasks.json | 42 ------------------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 2ee772ce16..0000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Build: HTML and binary", - "dependsOn": [ - "Build: HTML only", - "Build: binary only" - ], - "dependsOrder": "sequence", - "problemMatcher": [ - "$platformio", - ], - }, - { - "type": "PlatformIO", - "label": "Build: binary only", - "task": "Build", - "group": { - "kind": "build", - "isDefault": true, - }, - "problemMatcher": [ - "$platformio" - ], - "presentation": { - "panel": "shared" - } - }, - { - "type": "npm", - "script": "build", - "group": "build", - "problemMatcher": [], - "label": "Build: HTML only", - "detail": "npm run build", - "presentation": { - "panel": "shared" - } - } - ] -} \ No newline at end of file From 2d29a1852fb200837e7bedbc33685fc20d7e5240 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 13 Aug 2023 10:30:33 -0400 Subject: [PATCH 187/263] Documented the branch --- readme.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/readme.md b/readme.md index dda6634a1e..2394dd5412 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,8 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! +Now with new magical sync powers! + ## ⚙️ Features - WS2812FX library with more than 100 special effects - FastLED noise effects and 50 palettes @@ -59,6 +61,51 @@ See the [documentation on our official site](https://kno.wled.ge)! See [here](https://kno.wled.ge/basics/compatible-hardware)! +## WLEDtubes branch changes + +This is a WLED-based update to my [2019 light tube project](https://github.com/SteveEisner/tubes), which ran on Teensy + FastLED + nRF24L01 radios. + +Most of the changes are in the usermod `usermods/Tubes`: +* Tubes is installed as an overlay function, which allows it to run WLED "underneath" but completely control the output. +* Its final output is a composition of multiple layers: it starts with WLED's output, then optionally overwrites with its own patterns, then runs a particle effects library on top of that. +* It stores a curated playlist of composite effects. Some are WLED stock FX, others are custom-built. It moves randomly through that playlist. +* At the time of writing, WLED could not correctly fade transitions between 2 FX. Tubes makes up for that by being able to fade between 1 WLED FX and 1 custom overlay pattern, or between 2 custom overlays. As it moves through the playlist, it ensures that it never plays two WLED FX in a row. +* The particle library runs on top of everything (including WLED FX) and introduces a variety of patterns & blit effects. +* If the user changes effects or palettes in the WLED web UI, Tubes will honor that & stop overlaying for a little while. It will eventually time out and revert to full overlay mode. +* Everything runs on a custom clock that is synced to a specific BPM. The BPM can be changed (manually now, automatic eventually) so that the effects perfectly sync to nearby music. WLED FX don't sync yet but could be speed-adjusted. +* An ESP-Now based mesh network is created, so all devices running this usermod stay in sync with each other, without Wi-Fi. + +Mesh networking is based on a unidirectional broadcast protocol: +* Every device (node) is assigned a random 12-bit ID. This ID can change at any time, although in practice it only changes upon reboot. +* A node begins with the assumption that it is the only node, and therefore it is the leader, which means it's the one controlling patterns, palettes, and effects. +* As a node operates, it regularly broadcasts its status via ESP-Now, in case other device nodes are nearby. Nodes also continuously listen for ESP-Now broadcasts with node status. +* Status messages are identified by the node's ID. If a node receives a broadcast from a lower ID, it ignores it. +* When a node receives a broadcast from a higher ID, it assumes that other node must be the leader. It syncs its status to the leader's status & stops broadcasting its own status +* When a node receives a broadcast from the same ID, it assumes there's been an ID assignment collision and randomizes its own ID. (This happens sometimes even in a 12-bit space.) +* Nodes are assumed to be unstable; they can move or be turned off (or crash.) Status packets include both a current status and 30+ seconds of future states. All nodes can continue to run in sync even if they don't hear from the leader during this time. +* If a node hasn't heard from the leader in a long time (20 sec or more), it assumes the leader has permanently left. It reverts to being its own leader again until it hears from a new leader with higher ID. +* To help boost the leader's effective range, a following node will occasionally relay the leader's commands using the leader's ID. This helps sync devices that are out of range of the leader, but within range of a follower. The effective range of a single ESP32 device has been measured at hundreds feet; relays allow for an even larger mesh range. +* There's a protocol for explicit control, with commands that can be sent to specific nodes or all nodes. This allows a single master remote to directly control the entire mesh. +* This has been tested on 75+ devices in proximity, but theoretically can expand until it saturates ESPNow bandwidth (hundreds of devices? thousands? not sure) + +The Tubes usermod uses several sub-libraries and helper functions: +* beats.h: an 8-bit bpm library that helps the Tubes run patterns at a specific bpm +* node.h: the ESP-Now based mesh network +* particle.h: a particle effects overlay library +* firmware.sh: successful firmware+config mass-autoupdater +* master.h: a remote that overrides & controls all ESP-Now nodes (run from a separate device) +* timer.h: a tiny library to help with timed events + +There are several left-over modules that aren't used any more. +* bluetooth.h: a failed initial attempt to sync over BLE (now unused) +* updater.h + update_server.h: a failed attempt to write a peer-to-peer firmware auto-updater (unreliable) +* sound.h: an initial attempt to create some sound-reactive effect overlays + +Also, there a few changes to core library files: +* New brighter, more vivid color palettes (palettes.h + wled00/FX_fcn.cpp) +* New button-press code to allow a single button to handle Wi-Fi protection (it's only turned on by explicit button press) and "Power-save" for battery operation (wled00/button.cpp) +* Fleet provisioning for flashing dozens of WLED controllers (wled00/wled_serial.cpp disabled to allow it) + ## ✌️ Other Licensed under the MIT license From d687f83e86e0ad073deb54e057757ceb67abef80 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 13 Aug 2023 21:28:48 -0400 Subject: [PATCH 188/263] Remove use of "this" --- usermods/Tubes/Tubes.h | 20 +- usermods/Tubes/beats.h | 36 +- usermods/Tubes/controller.h | 590 ++++++++++++++++----------------- usermods/Tubes/debug.h | 56 ++-- usermods/Tubes/effects.h | 30 +- usermods/Tubes/global_state.h | 22 +- usermods/Tubes/led_strip.h | 24 +- usermods/Tubes/master.h | 82 +++-- usermods/Tubes/node.h | 114 ++++--- usermods/Tubes/options.h | 22 +- usermods/Tubes/particle.h | 66 ++-- usermods/Tubes/sound.h | 16 +- usermods/Tubes/timer.h | 34 +- usermods/Tubes/virtual_strip.h | 83 +++-- 14 files changed, 592 insertions(+), 603 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 773f259e08..6b43794151 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -82,8 +82,8 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { // Draw effects layers over whatever WLED is doing. - this->controller.handleOverlayDraw(); - this->debug.handleOverlayDraw(); + controller.handleOverlayDraw(); + debug.handleOverlayDraw(); if (master) master->handleOverlayDraw(); @@ -101,22 +101,22 @@ class TubesUsermod : public Usermod { bool handleButton(uint8_t b) { // Special code for handling the "power save" button if (b == 100) { // Press button 0 for WLED_LONG_POWER_SAVE ms - this->controller.togglePowerSave(); + controller.togglePowerSave(); return true; } if (b == 101) { // Short press button 0 (piggybacks with default) - this->controller.cancelOverrides(); + controller.cancelOverrides(); return true; } if (b == 102) { // Double-click button 0 - this->controller.acknowledge(); - if (this->controller.isSelecting()) { - if (this->controller.isSelected()) - this->controller.deselect(); + controller.acknowledge(); + if (controller.isSelecting()) { + if (controller.isSelected()) + controller.deselect(); else - this->controller.select(); + controller.select(); } else { - this->controller.request_new_bpm(); + controller.request_new_bpm(); } return true; } diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h index 67643f6eef..52924b8429 100644 --- a/usermods/Tubes/beats.h +++ b/usermods/Tubes/beats.h @@ -19,7 +19,7 @@ class BeatController { globalTimer.setup(); // Starts in phrase 1 - this->sync(DEFAULT_BPM << 8, 0); + sync(DEFAULT_BPM << 8, 0); } void update() @@ -27,41 +27,41 @@ class BeatController { globalTimer.update(); // Maintains an accumulator with 14 bits of precision - this->accum += globalTimer.delta_micros << 8; - while (this->accum > this->micros_per_frac) { - this->frac++; - this->accum -= this->micros_per_frac; + accum += globalTimer.delta_micros << 8; + while (accum > micros_per_frac) { + frac++; + accum -= micros_per_frac; } } void sync(accum88 bpm, BeatFrame_24_8 frac) { - accum88 last_bpm = this->bpm; - this->bpm = bpm; - this->frac = frac; - this->accum = 0; + accum88 last_bpm = bpm; + bpm = bpm; + frac = frac; + accum = 0; - this->micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); + micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); - if (last_bpm != this->bpm) - this->print_bpm(); + if (last_bpm != bpm) + print_bpm(); } void set_bpm(accum88 bpm) { - this->sync(bpm, this->frac); + sync(bpm, frac); } void adjust_bpm(saccum78 bpm) { - this->sync(this->bpm + bpm, this->frac); + sync(bpm + bpm, frac); } void start_phrase() { - this->frac &= -0xFFF; - this->accum = 0; + frac &= -0xFFF; + accum = 0; } void print_bpm() { - Serial.print(this->bpm >> 8); - uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(bpm >> 8); + uint8_t frac = scale8(100, bpm & 0xFF); Serial.print(F(".")); if (frac < 10) Serial.print(F("0")); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 294830df2b..b3fcda3b3e 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -77,25 +77,25 @@ class Button { bool lastPressed = false; void setup(uint8_t pin) { - this->pin = pin; + pin = pin; pinMode(pin, INPUT_PULLUP); - this->debounceTimer.start(0); + debounceTimer.start(0); } bool pressed() { - if (digitalRead(this->pin) == HIGH) { - return !this->debounceTimer.ended(); + if (digitalRead(pin) == HIGH) { + return !debounceTimer.ended(); } - this->debounceTimer.start(DEBOUNCE_TIME); + debounceTimer.start(DEBOUNCE_TIME); return true; } bool triggered() { // Triggers BOTH low->high AND high->low - bool p = this->pressed(); - bool lp = this->lastPressed; - this->lastPressed = p; + bool p = pressed(); + bool lp = lastPressed; + lastPressed = p; return p != lp; } }; @@ -144,158 +144,156 @@ class PatternController : public MessageReceiver { // When a pattern is boring, spice it up a bit with more effects bool isBoring = false; - PatternController(uint8_t num_leds, BeatController *beats) { - this->num_leds = num_leds; + PatternController(uint8_t num, BeatController *b) : num_leds(num), beats(b) { #ifdef USELCD - this->lcd = new Lcd(); + lcd = new Lcd(); #endif - this->led_strip = new LEDs(num_leds); - this->beats = beats; - this->effects = new Effects(); - this->node = new LightNode(this); - // this->mesh = new BLEMeshNode(this); + led_strip = new LEDs(num_leds); + effects = new Effects(); + node = new LightNode(this); + // mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED - this->vstrips[i] = new VirtualStrip(num_leds * 2 + 1); + vstrips[i] = new VirtualStrip(num_leds * 2 + 1); #else - this->vstrips[i] = new VirtualStrip(num_leds); + vstrips[i] = new VirtualStrip(num_leds); #endif } } bool isMaster() { - return this->role >= MasterRole; + return role >= MasterRole; } void setup() { - this->node->setup(); + node->setup(); EEPROM.begin(2560); - this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); - if (this->role == 255) { - this->role = UnknownRole; + role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + if (role == 255) { + role = UnknownRole; } EEPROM.end(); - Serial.printf("Role = %d\n", this->role); + Serial.printf("Role = %d\n", role); auto b = EEPROM.read(BOOT_OPTIONS_EEPROM_LOCATION); Serial.printf("EEPROM read: %d\n", b); BootOptions* boot = (BootOptions*)&b; switch (boot->default_power_save) { case BOOT_OPTION_POWER_SAVE_OFF: - this->power_save = 0; + power_save = 0; break; case BOOT_OPTION_POWER_SAVE_ON: - this->power_save = 1; + power_save = 1; break; default: - this->power_save = (this->role < CampRole); + power_save = (role < CampRole); break; } - if (this->role <= CampRole) + if (role <= CampRole) strip.ablMilliampsMax = 700; // Really limit for batteries - else if (this->role <= InstallationRole) + else if (role <= InstallationRole) strip.ablMilliampsMax = 1000; else strip.ablMilliampsMax = 1400; - if (this->role >= MasterRole) { - this->node->reset(3850 + this->role); // MASTER ID - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; - } else if (this->role >= LegacyRole) { - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - } else if (this->role == InstallationRole) { - this->options.brightness = DEFAULT_TANK_BRIGHTNESS; + if (role >= MasterRole) { + node->reset(3850 + role); // MASTER ID + options.brightness = DEFAULT_MASTER_BRIGHTNESS; + } else if (role >= LegacyRole) { + options.brightness = DEFAULT_TUBE_BRIGHTNESS; + } else if (role == InstallationRole) { + options.brightness = DEFAULT_TANK_BRIGHTNESS; } else { - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + options.brightness = DEFAULT_TUBE_BRIGHTNESS; } - this->options.debugging = false; - this->load_options(this->options); + options.debugging = false; + load_options(options); #ifdef USELCD - this->lcd->setup(); + lcd->setup(); #endif - this->set_next_pattern(0); - this->set_next_palette(0); - this->set_next_effect(0); - this->next_state.pattern_phrase = 0; - this->next_state.palette_phrase = 0; - this->next_state.effect_phrase = 0; - this->set_wled_palette(0); // Default palette - this->set_wled_pattern(0, 128, 128); // Default pattern - - this->sound.setup(); - - this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to + set_next_pattern(0); + set_next_palette(0); + set_next_effect(0); + next_state.pattern_phrase = 0; + next_state.palette_phrase = 0; + next_state.effect_phrase = 0; + set_wled_palette(0); // Default palette + set_wled_pattern(0, 128, 128); // Default pattern + + sound.setup(); + + updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Controller: ok"); } void do_pattern_changes() { - uint16_t phrase = this->current_state.beat_frame >> 12; + uint16_t phrase = current_state.beat_frame >> 12; bool changed = false; - if (phrase >= this->next_state.pattern_phrase) { + if (phrase >= next_state.pattern_phrase) { #ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change pattern"); #endif - this->load_pattern(this->next_state); - this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + load_pattern(next_state); + next_state.pattern_phrase = phrase + set_next_pattern(phrase); changed = true; } - if (phrase >= this->next_state.palette_phrase) { + if (phrase >= next_state.palette_phrase) { #ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change palette"); #endif - this->load_palette(this->next_state); - this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + load_palette(next_state); + next_state.palette_phrase = phrase + set_next_palette(phrase); changed = true; } - if (phrase >= this->next_state.effect_phrase) { + if (phrase >= next_state.effect_phrase) { #ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change effect"); #endif - this->load_effect(this->next_state); - this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + load_effect(next_state); + next_state.effect_phrase = phrase + set_next_effect(phrase); changed = true; } if (changed) { // For now, WLED doesn't handle transitioning pattern & palette well. // Stagger them - if (this->next_state.pattern_phrase == this->next_state.palette_phrase) { - this->next_state.palette_phrase += random8(1,3); + if (next_state.pattern_phrase == next_state.palette_phrase) { + next_state.palette_phrase += random8(1,3); } - this->next_state.print(); + next_state.print(); Serial.println(); } } void cancelOverrides() { // Release the WLED overrides and take over control of the strip again. - this->paletteOverrideTimer.stop(); - this->patternOverrideTimer.stop(); + paletteOverrideTimer.stop(); + patternOverrideTimer.stop(); } void enterSelectMode() { - this->selectTimer.start(20000); + selectTimer.start(20000); } bool isSelecting() { - return !this->selectTimer.ended(); + return !selectTimer.ended(); } bool isSelected() { - return this->updater.status == Ready; + return updater.status == Ready; } void select(bool selected = true) { if (selected) - this->updater.ready(); + updater.ready(); else { - this->updater.stop(); + updater.stop(); WiFi.softAPdisconnect(true); } } @@ -305,115 +303,115 @@ class PatternController : public MessageReceiver { } void set_palette_override(uint8_t value) { - if (!this->canOverride) + if (!canOverride) return; - if (value == this->paletteOverride) + if (value == paletteOverride) return; - this->paletteOverride = value; + paletteOverride = value; if (value) { Serial.println("WLED has control of palette."); - this->paletteOverrideTimer.start(300000); // 5 minutes of manual control + paletteOverrideTimer.start(300000); // 5 minutes of manual control } else { Serial.println("Turning off WLED control of palette."); - this->paletteOverrideTimer.stop(); - this->set_wled_palette(this->current_state.palette_id); + paletteOverrideTimer.stop(); + set_wled_palette(current_state.palette_id); } } void set_pattern_override(uint8_t value, uint8_t auto_mode) { - if (!this->canOverride) + if (!canOverride) return; - if (value == DEFAULT_WLED_FX && !this->patternOverride) + if (value == DEFAULT_WLED_FX && !patternOverride) return; - if (value == this->patternOverride) + if (value == patternOverride) return; - this->patternOverride = value; + patternOverride = value; if (value) { Serial.println("WLED has control of patterns."); - this->patternOverrideTimer.start(300000); // 5 minutes of manual control + patternOverrideTimer.start(300000); // 5 minutes of manual control transitionDelay = 500; // Short transitions } else { Serial.println("Turning off WLED control of patterns."); - this->patternOverrideTimer.stop(); + patternOverrideTimer.stop(); transitionDelay = 8000; // Back to long transitions uint8_t param = modeParameter(auto_mode); - this->set_wled_pattern(auto_mode, param, param); + set_wled_pattern(auto_mode, param, param); } } void update() { - this->read_keys(); + read_keys(); // Update the mesh - this->node->update(); + node->update(); // Update sound meter - this->sound.update(); + sound.update(); // Update patterns to the beat - this->update_beat(); + update_beat(); Segment& segment = strip.getMainSegment(); // You can only go into manual control after enabling the wifi - if (apActive && this->updater.status != Ready) - this->canOverride = true; + if (apActive && updater.status != Ready) + canOverride = true; // Detect manual overrides & update the current state to match. - if (this->canOverride) { - if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { - this->set_palette_override(0); - } else if (segment.palette != this->current_state.palette_id) { - this->set_palette_override(segment.palette); + if (canOverride) { + if (paletteOverride && (paletteOverrideTimer.ended() || !apActive)) { + set_palette_override(0); + } else if (segment.palette != current_state.palette_id) { + set_palette_override(segment.palette); } - uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; + uint8_t wled_mode = gPatterns[current_state.pattern_id].wled_fx_id; if (wled_mode < 10) wled_mode = DEFAULT_WLED_FX; - if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { - this->set_pattern_override(0, wled_mode); + if (patternOverride && (patternOverrideTimer.ended() || !apActive)) { + set_pattern_override(0, wled_mode); } else if (segment.mode != wled_mode) { - this->set_pattern_override(segment.mode, wled_mode); + set_pattern_override(segment.mode, wled_mode); } } do_pattern_changes(); - if (this->graphicsTimer.every(REFRESH_PERIOD)) { - this->updateGraphics(); + if (graphicsTimer.every(REFRESH_PERIOD)) { + updateGraphics(); } // Update current status - if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { + if (updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!this->node->is_following() || random(0, 4) == 0) { - this->send_update(); + if (!node->is_following() || random(0, 4) == 0) { + send_update(); } } - this->updater.update(); + updater.update(); #ifdef USELCD - if (this->lcd->active) { - this->lcd->size(1); - this->lcd->write(0,56, this->current_state.beat_frame); - this->lcd->write(80,56, this->x_axis); - this->lcd->write(100,56, this->y_axis); - this->lcd->show(); - - this->lcd->update(); + if (lcd->active) { + lcd->size(1); + lcd->write(0,56, current_state.beat_frame); + lcd->write(80,56, x_axis); + lcd->write(100,56, y_axis); + lcd->show(); + + lcd->update(); } #endif } void handleOverlayDraw() { // In manual mode WLED is always active - if (this->patternOverride) { - this->wled_fader = 0xFFFF; + if (patternOverride) { + wled_fader = 0xFFFF; transition_mode_point = 0; } else if (wled_fader == 0xFFFF) { // When fading down... @@ -429,11 +427,11 @@ class PatternController : public MessageReceiver { uint16_t length = strip.getLengthTotal(); // Crossfade between the custom pattern engine and WLED - uint8_t fader = this->wled_fader >> 8; + uint8_t fader = wled_fader >> 8; if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer for (int i = 0; i < length; i++) { - CRGB c = this->led_strip->getPixelColor(i); + CRGB c = led_strip->getPixelColor(i); if (fader > 0) { CRGB color2 = strip.getPixelColor(i); uint8_t r = blend8(c.r, color2.r, fader); @@ -447,7 +445,7 @@ class PatternController : public MessageReceiver { // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles - if (this->power_save && this->role < InstallationRole) { + if (power_save && role < InstallationRole) { // Screen door effect to save power for (int i = 0; i < length; i++) { if (i % 2) { @@ -456,34 +454,34 @@ class PatternController : public MessageReceiver { } } - this->sound.handleOverlayDraw(); + sound.handleOverlayDraw(); // Draw effects layers over whatever WLED is doing. // But not in manual (WLED) mode - if (!this->patternOverride) { - this->effects->draw(&strip); + if (!patternOverride) { + effects->draw(&strip); } // Make the art half-size if it has a small number of pixels - if (this->role >= MasterRole || this->role == SmallArtRole) { + if (role >= MasterRole || role == SmallArtRole) { int p = 0; for (int i = 0; i < length; i++) { CRGB c = strip.getPixelColor(i++); // i advances by 2 CRGB c2 = strip.getPixelColor(i); nblend(c, c2, 128); - if (this->role >= MasterRole) { + if (role >= MasterRole) { nblend(c, CRGB::Black, 128); } strip.setPixelColor(p++, c); } } - if (this->flashColor) { + if (flashColor) { if (flashTimer.ended()) - this->flashColor = 0; + flashColor = 0; else { if (millis() % 4000 < 2000) { - auto chsv = CHSV(this->flashColor, 255, 255); + auto chsv = CHSV(flashColor, 255, 255); for (int i = 0; i < length; i++) { strip.setPixelColor(i, CRGB(chsv)); } @@ -491,77 +489,77 @@ class PatternController : public MessageReceiver { } } - this->updater.handleOverlayDraw(); + updater.handleOverlayDraw(); } void restart_phrase() { - this->beats->start_phrase(); - this->update_beat(); - this->send_update(); + beats->start_phrase(); + update_beat(); + send_update(); } void set_phrase_position(uint8_t pos) { - this->beats->sync(this->beats->bpm, (this->beats->frac & -0xFFF) + (pos<<8)); - this->update_beat(); - this->send_update(); + beats->sync(beats->bpm, (beats->frac & -0xFFF) + (pos<<8)); + update_beat(); + send_update(); } void set_tapped_bpm(accum88 bpm, uint8_t pos=15) { // By default, restarts at 15th beat - because this is the end of a tap - this->beats->sync(bpm, (this->beats->frac & -0xFFF) + (pos<<8)); - this->update_beat(); - this->send_update(); + beats->sync(bpm, (beats->frac & -0xFFF) + (pos<<8)); + update_beat(); + send_update(); } void request_new_bpm(accum88 new_bpm = 0) { // 0 = toggle 120 to 125 if (new_bpm == 0) - new_bpm = this->current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; + new_bpm = current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; - if (this->node->is_following()) { + if (node->is_following()) { // Send a request up to ROOT - this->broadcast_bpm(new_bpm); + broadcast_bpm(new_bpm); } else { - this->set_tapped_bpm(new_bpm, 0); + set_tapped_bpm(new_bpm, 0); } } void update_beat() { - this->current_state.bpm = this->next_state.bpm = this->beats->bpm; - this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) - if (this->current_state.bpm>>8 <= 118) // Hip hop / ghettofunk - this->energy = MediumEnergy; - else if (this->current_state.bpm>>8 >= 125) // House & breaks - this->energy = HighEnergy; - else if (this->current_state.bpm>>8 > 120) // Tech house - this->energy = MediumEnergy; + current_state.bpm = next_state.bpm = beats->bpm; + current_state.beat_frame = particle_beat_frame = beats->frac; // (particle_beat_frame is a hack) + if (current_state.bpm>>8 <= 118) // Hip hop / ghettofunk + energy = MediumEnergy; + else if (current_state.bpm>>8 >= 125) // House & breaks + energy = HighEnergy; + else if (current_state.bpm>>8 > 120) // Tech house + energy = MediumEnergy; else - this->energy = Chill; // Deep house + energy = Chill; // Deep house } void send_update() { Serial.print(" "); - this->current_state.print(); + current_state.print(); Serial.print(F(" ")); - uint16_t phrase = this->current_state.beat_frame >> 12; + uint16_t phrase = current_state.beat_frame >> 12; Serial.print(F(" ")); - Serial.print(this->next_state.pattern_phrase - phrase); + Serial.print(next_state.pattern_phrase - phrase); Serial.print(F("P ")); - Serial.print(this->next_state.palette_phrase - phrase); + Serial.print(next_state.palette_phrase - phrase); Serial.print(F("C ")); - Serial.print(this->next_state.effect_phrase - phrase); + Serial.print(next_state.effect_phrase - phrase); Serial.print(F("E: ")); - this->next_state.print(); + next_state.print(); Serial.print(F(" ")); Serial.println(); - this->broadcast_state(); + broadcast_state(); } void background_changed() { - this->update_background(); - this->current_state.print(); + update_background(); + current_state.print(); Serial.println(); } @@ -570,25 +568,25 @@ class PatternController : public MessageReceiver { } void load_pattern(TubeState &tube_state) { - if (this->current_state.pattern_id == tube_state.pattern_id - && this->current_state.pattern_sync_id == tube_state.pattern_sync_id) + if (current_state.pattern_id == tube_state.pattern_id + && current_state.pattern_sync_id == tube_state.pattern_sync_id) return; - this->current_state.pattern_phrase = tube_state.pattern_phrase; - this->current_state.pattern_id = tube_state.pattern_id % gPatternCount; - this->current_state.pattern_sync_id = tube_state.pattern_sync_id; - this->isBoring = gPatterns[this->current_state.pattern_id].control.energy == Boring; + current_state.pattern_phrase = tube_state.pattern_phrase; + current_state.pattern_id = tube_state.pattern_id % gPatternCount; + current_state.pattern_sync_id = tube_state.pattern_sync_id; + isBoring = gPatterns[current_state.pattern_id].control.energy == Boring; Serial.print(F("Change pattern ")); - this->background_changed(); + background_changed(); } bool isShowingWled() { - return this->current_state.pattern_id >= numInternalPatterns; + return current_state.pattern_id >= numInternalPatterns; } uint8_t modeParameter(uint8_t mode) { - switch (this->energy) { + switch (energy) { case Boring: // Spice things up a bit return 128; @@ -626,15 +624,15 @@ class PatternController : public MessageReceiver { for (int i = 0; i < 10; i++) { pattern_id = get_valid_next_pattern(); def = gPatterns[pattern_id]; - if (def.control.energy <= this->energy) + if (def.control.energy <= energy) break; } #ifdef IDENTIFY_STUCK_PATTERNS Serial.printf("Next pattern will be %d\n", pattern_id); #endif - this->next_state.pattern_id = pattern_id; - this->next_state.pattern_sync_id = this->randomSyncMode(); + next_state.pattern_id = pattern_id; + next_state.pattern_sync_id = randomSyncMode(); switch (def.control.duration) { case ExtraShortDuration: return random8(2, 6); @@ -647,45 +645,45 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) + if (current_state.palette_id == tube_state.palette_id) return; - this->current_state.palette_phrase = tube_state.palette_phrase; - this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; - set_wled_palette(this->current_state.palette_id); + current_state.palette_phrase = tube_state.palette_phrase; + current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; + set_wled_palette(current_state.palette_id); } // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { // Don't select the built-in palettes - this->next_state.palette_id = random8(6, gGradientPaletteCount); + next_state.palette_id = random8(6, gGradientPaletteCount); auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); // Change color more often in boring patterns - if (this->isBoring) { + if (isBoring) { phrases /= 2; } return phrases; } void load_effect(TubeState &tube_state) { - if (this->current_state.effect_params.effect == tube_state.effect_params.effect && - this->current_state.effect_params.pen == tube_state.effect_params.pen && - this->current_state.effect_params.chance == tube_state.effect_params.chance) + if (current_state.effect_params.effect == tube_state.effect_params.effect && + current_state.effect_params.pen == tube_state.effect_params.pen && + current_state.effect_params.chance == tube_state.effect_params.chance) return; - this->_load_effect(tube_state.effect_params); + _load_effect(tube_state.effect_params); } void _load_effect(EffectParameters params) { - this->current_state.effect_params = params; + current_state.effect_params = params; Serial.print(F("Change effect ")); - this->current_state.print(); + current_state.print(); Serial.println(); - this->effects->load(this->current_state.effect_params); + effects->load(current_state.effect_params); } // Choose the effect to display at the next effect cycle @@ -695,11 +693,11 @@ class PatternController : public MessageReceiver { // Pick a random effect to add; boring patterns get better chance at having an effect. EffectDef def = gEffects[effect_num]; - if (def.control.energy > this->energy) { + if (def.control.energy > energy) { def = gEffects[0]; } - this->next_state.effect_params = def.params; + next_state.effect_params = def.params; switch (def.control.duration) { case ExtraShortDuration: return random(1,3); @@ -713,20 +711,20 @@ class PatternController : public MessageReceiver { void update_background() { Background background; - background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; - background.palette_id = this->current_state.palette_id; - background.sync = (SyncMode)this->current_state.pattern_sync_id; + background.animate = gPatterns[current_state.pattern_id].backgroundFn; + background.wled_fx_id = gPatterns[current_state.pattern_id].wled_fx_id; + background.palette_id = current_state.palette_id; + background.sync = (SyncMode)current_state.pattern_sync_id; // Use one of the virtual strips to render the patterns. // A WLED-based pattern exists on the virtual strip, but causes // it to do nothing since WLED merging happens in handleOverlayDraw. // Reuse virtual strips to prevent heap fragmentation for (uint8_t i = 0; i < NUM_VSTRIPS; i++) { - this->vstrips[i]->fadeOut(); + vstrips[i]->fadeOut(); } - this->vstrips[this->next_vstrip]->load(background); - this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; + vstrips[next_vstrip]->load(background); + next_vstrip = (next_vstrip + 1) % NUM_VSTRIPS; uint8_t param = modeParameter(background.wled_fx_id); set_wled_pattern(background.wled_fx_id, param, param); @@ -734,12 +732,12 @@ class PatternController : public MessageReceiver { } bool isUnderWledControl() { - return this->paletteOverride || this->patternOverride; + return paletteOverride || patternOverride; } void set_wled_palette(uint8_t palette_id) { - if (this->paletteOverride) - palette_id = this->paletteOverride; + if (paletteOverride) + palette_id = paletteOverride; for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); @@ -753,13 +751,13 @@ class PatternController : public MessageReceiver { } void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { - if (this->patternOverride) - pattern_id = this->patternOverride; + if (patternOverride) + pattern_id = patternOverride; else if (pattern_id == 0) pattern_id = DEFAULT_WLED_FX; // Never set it to solid // When fading IN, make the pattern transition immediate if possible - bool fadeIn = this->wled_fader < 2000; + bool fadeIn = wled_fader < 2000; for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; @@ -784,24 +782,24 @@ class PatternController : public MessageReceiver { void setBrightness(uint8_t brightness) { Serial.printf("brightness: %d\n", brightness); - this->options.brightness = brightness; - this->broadcast_options(); + options.brightness = brightness; + broadcast_options(); } void setDebugging(bool debugging) { Serial.printf("debugging: %d\n", debugging); - this->options.debugging = debugging; - this->broadcast_options(); + options.debugging = debugging; + broadcast_options(); } void togglePowerSave() { - setPowerSave(!this->power_save); + setPowerSave(!power_save); } void setPowerSave(bool power_save) { Serial.printf("power_save: %d\n", power_save); - this->power_save = power_save; + power_save = power_save; // Remember this setting on the next boot EEPROM.begin(2560); @@ -817,7 +815,7 @@ class PatternController : public MessageReceiver { } void setRole(ControllerRole role) { - this->role = role; + role = role; Serial.printf("Role = %d", role); EEPROM.begin(2560); EEPROM.write(ROLE_EEPROM_LOCATION, role); @@ -831,7 +829,7 @@ class PatternController : public MessageReceiver { uint8_t r = random8(128); // For boring patterns, up the chance of a sync mode - if (this->isBoring) + if (isBoring) r -= 20; if (r < 30) @@ -847,7 +845,7 @@ class PatternController : public MessageReceiver { void updateGraphics() { static BeatFrame_24_8 lastFrame = 0; - BeatFrame_24_8 beat_frame = this->current_state.beat_frame; + BeatFrame_24_8 beat_frame = current_state.beat_frame; uint8_t beat_pulse = 0; for (int i = 0; i < 8; i++) { @@ -856,11 +854,11 @@ class PatternController : public MessageReceiver { } lastFrame = beat_frame; - this->wled_fader = 0; + wled_fader = 0; VirtualStrip *first_strip = NULL; for (uint8_t i=0; i < NUM_VSTRIPS; i++) { - VirtualStrip *vstrip = this->vstrips[i]; + VirtualStrip *vstrip = vstrips[i]; if (vstrip->fade == Dead) continue; @@ -870,13 +868,13 @@ class PatternController : public MessageReceiver { // Remember the strip that's actually WLED if (vstrip->isWled()) - this->wled_fader = vstrip->fader; + wled_fader = vstrip->fader; vstrip->update(beat_frame, beat_pulse); - vstrip->blend(this->led_strip->leds, this->led_strip->num_leds, this->options.brightness, vstrip == first_strip); + vstrip->blend(led_strip->leds, led_strip->num_leds, options.brightness, vstrip == first_strip); } - this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); + effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); } virtual void acknowledge() { @@ -888,14 +886,14 @@ class PatternController : public MessageReceiver { return; char c = Serial.read(); - char *k = this->key_buffer; - uint8_t max = sizeof(this->key_buffer); + char *k = key_buffer; + uint8_t max = sizeof(key_buffer); for (uint8_t i=0; *k && (i < max-1); i++) { k++; } if (c == 10) { - this->keyboard_command(this->key_buffer); - this->key_buffer[0] = 0; + keyboard_command(key_buffer); + key_buffer[0] = 0; } else { *k++ = c; *k = 0; @@ -931,37 +929,37 @@ class PatternController : public MessageReceiver { void keyboard_command(char *command) { // If not the lead, send it to the lead. uint8_t b; - accum88 arg = this->parse_number(command+1); + accum88 arg = parse_number(command+1); switch (command[0]) { case 'd': - this->setDebugging(!this->options.debugging); + setDebugging(!options.debugging); break; case '~': doReboot = true; break; case '@': - this->togglePowerSave(); + togglePowerSave(); break; case '-': - b = this->options.brightness; + b = options.brightness; while (*command++ == '-') b -= 5; - this->setBrightness(b - 5); + setBrightness(b - 5); break; case '+': - b = this->options.brightness; + b = options.brightness; while (*command++ == '+') b += 5; - this->setBrightness(b + 5); + setBrightness(b + 5); return; case 'l': if (arg < 5*256) { Serial.println(F("nope")); return; } - this->setBrightness(arg >> 8); + setBrightness(arg >> 8); return; case 'b': @@ -973,51 +971,51 @@ class PatternController : public MessageReceiver { return; case 's': - this->beats->start_phrase(); - this->update_beat(); - this->send_update(); + beats->start_phrase(); + update_beat(); + send_update(); return; case 'n': - this->force_next(); + force_next(); return; case 'p': - this->next_state.pattern_phrase = 0; - this->next_state.pattern_id = arg >> 8; - this->next_state.pattern_sync_id = All; - this->broadcast_state(); + next_state.pattern_phrase = 0; + next_state.pattern_id = arg >> 8; + next_state.pattern_sync_id = All; + broadcast_state(); return; case 'm': - this->next_state.pattern_phrase = 0; - this->next_state.pattern_id = this->current_state.pattern_id; - this->next_state.pattern_sync_id = arg >> 8; - this->broadcast_state(); + next_state.pattern_phrase = 0; + next_state.pattern_id = current_state.pattern_id; + next_state.pattern_sync_id = arg >> 8; + broadcast_state(); return; case 'c': - this->next_state.palette_phrase = 0; - this->next_state.palette_id = arg >> 8; - this->broadcast_state(); + next_state.palette_phrase = 0; + next_state.palette_id = arg >> 8; + broadcast_state(); return; case 'e': - this->next_state.effect_phrase = 0; - this->next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; - this->broadcast_state(); + next_state.effect_phrase = 0; + next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; + broadcast_state(); return; case '%': - this->next_state.effect_phrase = 0; - this->next_state.effect_params = this->current_state.effect_params; - this->next_state.effect_params.chance = arg; - this->broadcast_state(); + next_state.effect_phrase = 0; + next_state.effect_params = current_state.effect_params; + next_state.effect_params.chance = arg; + broadcast_state(); return; case 'i': Serial.printf("Reset! ID -> %03X\n", arg >> 4); - this->node->reset(arg >> 4); + node->reset(arg >> 4); return; case 'U': @@ -1036,7 +1034,7 @@ class PatternController : public MessageReceiver { .key = command[0], .arg = (uint8_t)(arg >> 8) }; - this->broadcast_action(action); + broadcast_action(action); return; } @@ -1044,9 +1042,9 @@ class PatternController : public MessageReceiver { // Toggle power save Action action = { .key = command[0], - .arg = !this->power_save, + .arg = !power_save, }; - this->broadcast_action(action); + broadcast_action(action); break; } @@ -1054,14 +1052,14 @@ class PatternController : public MessageReceiver { // Toggle sound overlay Action action = { .key = command[0], - .arg = !this->sound.overlay + .arg = !sound.overlay }; - this->broadcast_action(action); + broadcast_action(action); break; } case 'r': - this->setRole((ControllerRole)(arg >> 8)); + setRole((ControllerRole)(arg >> 8)); return; case '?': @@ -1090,7 +1088,7 @@ class PatternController : public MessageReceiver { return; case 'u': - this->updater.start(); + updater.start(); return; case 0: @@ -1104,40 +1102,40 @@ class PatternController : public MessageReceiver { } void force_next() { - uint16_t phrase = this->current_state.beat_frame >> 12; - uint16_t next_phrase = min(this->next_state.pattern_phrase, min(this->next_state.palette_phrase, this->next_state.effect_phrase)) - phrase; - this->next_state.pattern_phrase -= next_phrase; - this->next_state.palette_phrase -= next_phrase; - this->next_state.effect_phrase -= next_phrase; - this->broadcast_state(); + uint16_t phrase = current_state.beat_frame >> 12; + uint16_t next_phrase = min(next_state.pattern_phrase, min(next_state.palette_phrase, next_state.effect_phrase)) - phrase; + next_state.pattern_phrase -= next_phrase; + next_state.palette_phrase -= next_phrase; + next_state.effect_phrase -= next_phrase; + broadcast_state(); } void broadcast_action(Action& action) { - if (!this->node->is_following()) { - this->onAction(&action); + if (!node->is_following()) { + onAction(&action); } - this->node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); + node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); } void broadcast_info(NodeInfo *info) { - this->node->sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); + node->sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); } void broadcast_state() { - this->node->sendCommand(COMMAND_STATE, &this->current_state, sizeof(TubeStates)); + node->sendCommand(COMMAND_STATE, ¤t_state, sizeof(TubeStates)); } void broadcast_options() { - this->node->sendCommand(COMMAND_OPTIONS, &this->options, sizeof(this->options)); + node->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } void broadcast_autoupdate() { - this->node->sendCommand(COMMAND_UPGRADE, &this->updater.current_version, sizeof(this->updater.current_version)); + node->sendCommand(COMMAND_UPGRADE, &updater.current_version, sizeof(updater.current_version)); } void broadcast_bpm(accum88 bpm) { // Hacked in feature: request a new BPM - this->node->sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); + node->sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); } virtual bool onCommand(CommandId command, void *data) { @@ -1149,11 +1147,11 @@ class PatternController : public MessageReceiver { return true; case COMMAND_OPTIONS: - memcpy(&this->options, data, sizeof(this->options)); - this->load_options(this->options); + memcpy(&options, data, sizeof(options)); + load_options(options); Serial.printf("[debug=%d bri=%d]", - this->options.debugging, - this->options.brightness + options.debugging, + options.brightness ); return true; @@ -1162,32 +1160,32 @@ class PatternController : public MessageReceiver { TubeState state; memcpy(&state, &update_data->current, sizeof(TubeState)); - memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); + memcpy(&next_state, &update_data->next, sizeof(TubeState)); state.print(); - this->next_state.print(); + next_state.print(); // Catch up to this state - this->load_pattern(state); - this->load_palette(state); - this->load_effect(state); - this->beats->sync(state.bpm, state.beat_frame); + load_pattern(state); + load_palette(state); + load_effect(state); + beats->sync(state.bpm, state.beat_frame); return true; } case COMMAND_UPGRADE: - this->updater.start((AutoUpdateOffer*)data); + updater.start((AutoUpdateOffer*)data); return true; case COMMAND_ACTION: - this->onAction((Action*)data); + onAction((Action*)data); return true; case COMMAND_BEATS: // the master control ignores this request, it has its own // beat measuring. - if (this->isMaster()) + if (isMaster()) return false; - this->set_tapped_bpm(*(accum88*)data, 0); + set_tapped_bpm(*(accum88*)data, 0); return true; } @@ -1203,24 +1201,24 @@ class PatternController : public MessageReceiver { return; case 'O': - this->sound.overlay = (action->arg != 0); + sound.overlay = (action->arg != 0); return; case 'X': - if (!this->isSelected()) + if (!isSelected()) return; doReboot = true; return; case 'R': - if (!this->isSelected()) + if (!isSelected()) return; setRole((ControllerRole)(action->arg)); return; case '@': Serial.print("Setting power save to %d\n"); - this->setPowerSave(action->arg); + setPowerSave(action->arg); return; case 'W': @@ -1238,38 +1236,38 @@ class PatternController : public MessageReceiver { case 'F': Serial.println("flash!"); - this->flashTimer.start(20000); - this->flashColor = action->arg; + flashTimer.start(20000); + flashColor = action->arg; return; case 'M': Serial.println("cancel manual mode"); - this->cancelOverrides(); + cancelOverrides(); break; case '*': case '(': Serial.println("enter select mode"); - this->enterSelectMode(); + enterSelectMode(); break; case ')': Serial.println("exit select mode"); - this->deselect(); + deselect(); break; case 'V': // Version check: prepare for update - if (this->updater.current_version.version >= action->arg) + if (updater.current_version.version >= action->arg) break; - this->select(); + select(); break; case 'U': - if (!this->isSelected()) + if (!isSelected()) return; - this->updater.start(); + updater.start(); break; } diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 571b07b0e3..c6870f444c 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -30,16 +30,16 @@ class DebugController { uint32_t lastPhraseTime; uint32_t lastFrame; - DebugController(PatternController *controller) { - this->controller = controller; - this->led_strip = controller->led_strip; - this->node = controller->node; + DebugController(PatternController *c) : controller(c) + { + led_strip = controller->led_strip; + node = controller->node; } void setup() { - this->lastPhraseTime = globalTimer.now_micros; - this->lastFrame = (uint32_t)-1; + lastPhraseTime = globalTimer.now_micros; + lastFrame = (uint32_t)-1; } std::string status_code(NodeStatus status) { @@ -62,8 +62,8 @@ class DebugController { auto knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); auto knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); Serial.printf("\n=== %s%s WiFi[ch%d] %s IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n", - this->controller->node->node_name, - status_code(this->controller->node->status).c_str(), + controller->node->node_name, + status_code(controller->node->status).c_str(), WiFi.channel(), knownSsid.c_str(), knownIp[0], @@ -77,15 +77,15 @@ class DebugController { ); Serial.printf("=== Controller: "); - if (this->controller->isMaster()) { + if (controller->isMaster()) { Serial.print("PRIMARY "); } - if (this->controller->sound.active) { + if (controller->sound.active) { Serial.print("SOUND "); } Serial.printf("role=%d power_save=%d\n", - this->controller->role, - this->controller->power_save + controller->role, + controller->power_save ); // Dump WLED status @@ -102,24 +102,24 @@ class DebugController { seg.speed, seg.intensity ); - if (this->controller->patternOverride) { - Serial.printf(" (PATTERN %d)", this->controller->patternOverride); + if (controller->patternOverride) { + Serial.printf(" (PATTERN %d)", controller->patternOverride); } else { - Serial.printf(" at %d", this->controller->wled_fader); + Serial.printf(" at %d", controller->wled_fader); } - if (this->controller->paletteOverride) { - Serial.printf(" (PALETTE %d)", this->controller->paletteOverride); + if (controller->paletteOverride) { + Serial.printf(" (PALETTE %d)", controller->paletteOverride); } Serial.println(); Serial.printf("=== firmware: v%d from SSID %s %u.%u.%u.%u OTA=%d\n\n", - this->controller->updater.current_version.version, - this->controller->updater.current_version.ssid, - this->controller->updater.current_version.host[0], - this->controller->updater.current_version.ssid[1], - this->controller->updater.current_version.ssid[2], - this->controller->updater.current_version.ssid[3], - this->controller->updater.status + controller->updater.current_version.version, + controller->updater.current_version.ssid, + controller->updater.current_version.host[0], + controller->updater.current_version.ssid[1], + controller->updater.current_version.ssid[2], + controller->updater.current_version.ssid[3], + controller->updater.status ); } @@ -128,16 +128,16 @@ class DebugController { void handleOverlayDraw() { // Show the beat on the master OR if debugging - if (this->controller->options.debugging) { + if (controller->options.debugging) { uint16_t num_leds = strip.getLengthTotal(); - uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; + uint8_t p1 = (controller->current_state.beat_frame >> 8) % 16; strip.setPixelColor(p1, CRGB::White); - uint8_t p2 = scale8(this->controller->node->header.id>>4, num_leds-1); + uint8_t p2 = scale8(controller->node->header.id>>4, num_leds-1); strip.setPixelColor(p2, CRGB::Yellow); - uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, num_leds-1); + uint8_t p3 = scale8(controller->node->header.uplinkId>>4, num_leds-1); if (p3 == p2) { strip.setPixelColor(p3, CRGB::Green); } else { diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 22881c771b..baceddafec 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -54,49 +54,49 @@ class Effects { uint8_t chance; void load(EffectParameters ¶ms) { - this->effect = params.effect; - this->pen = params.pen; - this->beat = params.beat; - this->chance = params.chance; + effect = params.effect; + pen = params.pen; + beat = params.beat; + chance = params.chance; } void update(VirtualStrip *strip, BeatFrame_24_8 beat_frame, BeatPulse beat_pulse) { - if (!this->beat || beat_pulse & this->beat) { + if (!beat || beat_pulse & beat) { - if (random8() <= this->chance) { + if (random8() <= chance) { CRGB color = strip->palette_color(random8()); - switch (this->effect) { + switch (effect) { case None: break; case Glitter: - addGlitter(color, this->pen); + addGlitter(color, pen); break; case Beatbox1: case Beatbox2: - addBeatbox(color, this->pen); - if (this->effect == Beatbox2) - addBeatbox(color, this->pen); + addBeatbox(color, pen); + if (effect == Beatbox2) + addBeatbox(color, pen); break; case Bubble: - addBubble(color, this->pen); + addBubble(color, pen); break; case Spark: - addSpark(color, this->pen); + addSpark(color, pen); break; case Flash: - addFlash(CRGB::White, this->pen); + addFlash(CRGB::White, pen); break; } } } - this->animate(beat_frame, beat_pulse); + animate(beat_frame, beat_pulse); } void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index ba1b61d4bd..1677b74de9 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -22,28 +22,28 @@ class TubeState { EffectParameters effect_params; void print() { - uint16_t phrase = this->beat_frame >> 12; + uint16_t phrase = beat_frame >> 12; Serial.print(F("[")); Serial.print(phrase); Serial.print(F(".")); - Serial.print((this->beat_frame >> 8) % 16); + Serial.print((beat_frame >> 8) % 16); Serial.print(F(" P")); - Serial.print(this->pattern_id); + Serial.print(pattern_id); Serial.print(F(",")); - Serial.print(this->pattern_sync_id); + Serial.print(pattern_sync_id); Serial.print(F(" C")); - Serial.print(this->palette_id); + Serial.print(palette_id); Serial.print(F(" E")); - Serial.print(this->effect_params.effect); + Serial.print(effect_params.effect); Serial.print(F(",")); - Serial.print(this->effect_params.pen); + Serial.print(effect_params.pen); Serial.print(F(",")); - Serial.print(this->effect_params.beat); + Serial.print(effect_params.beat); Serial.print(F(",")); - Serial.print(this->effect_params.chance); + Serial.print(effect_params.chance); Serial.print(F(" ")); - Serial.print(this->bpm >> 8); - uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(bpm >> 8); + uint8_t frac = scale8(100, bpm & 0xFF); Serial.print(F(".")); if (frac < 10) Serial.print(F("0")); diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 96830f87e8..7edf523956 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -17,9 +17,7 @@ class LEDs { uint16_t fps = 0; - LEDs(int num_leds=MAX_REAL_LEDS) { - this->num_leds = num_leds; - } + LEDs(int num=MAX_REAL_LEDS) : num_leds(num) { }; void setup() { Serial.println((char *)F("LEDs: ok")); @@ -27,28 +25,28 @@ class LEDs { void reverse() { for (int i=1; i<8; i++) { - CRGB c = this->leds[i]; - this->leds[i] = this->leds[16-i]; - this->leds[16-i] = c; + CRGB c = leds[i]; + leds[i] = leds[16-i]; + leds[16-i] = c; } } void update() { - EVERY_N_MILLISECONDS( this->TARGET_REFRESH ) { - this->fps++; + EVERY_N_MILLISECONDS( TARGET_REFRESH ) { + fps++; } EVERY_N_MILLISECONDS( 1000 ) { - if (this->fps < (TARGET_FRAMES_PER_SECOND - 30)) { - Serial.print(this->fps); + if (fps < (TARGET_FRAMES_PER_SECOND - 30)) { + Serial.print(fps); Serial.println((char *)F(" fps!")); } - this->fps = 0; + fps = 0; } } CRGB getPixelColor(uint8_t pos) { - if (pos > this->num_leds) + if (pos > num_leds) return CRGB::Black; - return this->leds[pos]; + return leds[pos]; } }; diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index b341b73481..5549076d0e 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -28,16 +28,14 @@ class Master { PatternController *controller; Button button[5]; - Master(PatternController *controller) { - this->controller = controller; - } + Master(PatternController *c) : controller(c) { }; void setup() { - this->button[0].setup(BUTTON_PIN_1); - this->button[1].setup(BUTTON_PIN_2); - this->button[2].setup(BUTTON_PIN_3); - this->button[3].setup(BUTTON_PIN_4); - this->button[4].setup(BUTTON_PIN_5); + button[0].setup(BUTTON_PIN_1); + button[1].setup(BUTTON_PIN_2); + button[2].setup(BUTTON_PIN_3); + button[3].setup(BUTTON_PIN_4); + button[4].setup(BUTTON_PIN_5); Serial.println((char *)F("Master: ok")); } @@ -45,24 +43,24 @@ class Master { for (uint8_t i=0; i < 5; i++) { if (button[i].triggered()) { if (button[i].pressed()) - this->onButtonPress(i); + onButtonPress(i); else - this->onButtonRelease(i); + onButtonRelease(i); } } - if (this->taps && this->perTapTimer.ended()) { - if (this->taps == 2) { - this->ok(); + if (taps && perTapTimer.ended()) { + if (taps == 2) { + ok(); } else { - this->fail(); + fail(); } - this->taps = 0; + taps = 0; } } void handleOverlayDraw() { - this->updateStatus(this->controller); + updateStatus(controller); } void ok() { @@ -79,13 +77,13 @@ class Master { if (button == 4) { Serial.println((char *)F("Skip >>")); - this->controller->force_next(); - this->ok(); + controller->force_next(); + ok(); return; } if (button == 3) { - this->tap(); + tap(); return; } @@ -96,16 +94,16 @@ class Master { void onButtonRelease(uint8_t button) { #ifdef EXTRA_STUFF if (button == 2) { - if (this->palette_mode) - this->controller->_load_palette(this->palette_id); - this->palette_mode = false; + if (palette_mode) + controller->_load_palette(palette_id); + palette_mode = false; } #endif if (button == 3) { - if (this->taps == 0) + if (taps == 0) return; - this->tap(); + tap(); return; } @@ -114,27 +112,27 @@ class Master { } void tap() { - if (!this->taps) { - this->tapTimer.start(0); + if (!taps) { + tapTimer.start(0); } - this->perTapTimer.start(1500); + perTapTimer.start(1500); - uint32_t time = this->tapTimer.since_mark(); - this->tapTime[this->taps++] = time; + uint32_t time = tapTimer.since_mark(); + tapTime[taps++] = time; uint32_t bpm = 0; - if (this->taps > 4) { + if (taps > 4) { // Can study this later to make BPM detection better // Should be 60000; fudge a bit to adjust to real-world timings - bpm = 60220*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat + bpm = 60220*256*(taps-1) / time; // 120 beats per min = 500ms per beat if (bpm < 70*256) bpm *= 2; else if (bpm > 140*256) bpm /= 2; } - Serial.printf("tap %d: ", this->taps); + Serial.printf("tap %d: ", taps); Serial.print(bpm >> 8); uint8_t f = scale8(100, bpm & 0xFF); Serial.print("."); @@ -142,9 +140,9 @@ class Master { Serial.print("0"); Serial.println(f); - if (this->taps == 16) { + if (taps == 16) { Serial.println("OK! taps"); - this->taps = 0; + taps = 0; auto frac = bpm % 256; // Slight snap to beat @@ -153,18 +151,18 @@ class Master { else if (frac > 128) bpm += (256-frac) / 2; - this->controller->set_tapped_bpm(bpm); - this->ok(); - } else if (this->taps >= 2) { - this->controller->set_tapped_bpm(this->controller->current_state.bpm, taps-1); + controller->set_tapped_bpm(bpm); + ok(); + } else if (taps >= 2) { + controller->set_tapped_bpm(controller->current_state.bpm, taps-1); } } void updateStatus(PatternController *controller) { - if (this->taps) { - this->displayProgress(this->taps); - } else if (this->palette_mode) { - this->displayPalette(this->background); + if (taps) { + displayProgress(taps); + } else if (palette_mode) { + displayPalette(background); } else { uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; strip.setPixelColor(15 - beat_pos, CRGB::White); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f40b2e8230..dfdc9d3536 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -102,41 +102,39 @@ class LightNode { Timer uplinkTimer; // When this timer ends, assume uplink is lost. Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink - LightNode(MessageReceiver *receiver) { + LightNode(MessageReceiver *r) : receiver(r) { LightNode::instance = this; - - this->receiver = receiver; } void onWifiConnect() { - if (this->status == NODE_STATUS_QUIET) + if (status == NODE_STATUS_QUIET) return; Serial.println("WiFi connected: stop broadcasting"); quickEspNow.stop(); - this->status = NODE_STATUS_QUIET; - this->rebroadcastTimer.stop(); - this->statusTimer.start(WIFI_CHECK_RATE); + status = NODE_STATUS_QUIET; + rebroadcastTimer.stop(); + statusTimer.start(WIFI_CHECK_RATE); } void onWifiDisconnect() { - if (this->status != NODE_STATUS_QUIET) + if (status != NODE_STATUS_QUIET) return; Serial.println("WiFi disconnected: start broadcasting"); WiFi.mode (WIFI_MODE_STA); WiFi.disconnect(false, true); quickEspNow.begin(1, WIFI_IF_STA); - this->start(); + start(); } void onMeshChange() { - sprintf(this->node_name, + sprintf(node_name, "Tube %03X:%03X", - this->header.id, - this->header.uplinkId + header.id, + header.uplinkId ); - this->configure_ap(); + configure_ap(); } void configure_ap() { @@ -156,40 +154,40 @@ class LightNode { void start() { // Initialization timer: wait for a bit before trying to broadcast. // If this node's ID is high, it's more likely to be the leader, so wait less. - this->status = NODE_STATUS_STARTING; - this->statusTimer.start(3000 - this->header.id / 2); - this->rebroadcastTimer.stop(); + status = NODE_STATUS_STARTING; + statusTimer.start(3000 - header.id / 2); + rebroadcastTimer.stop(); } void onPeerPing(MeshNodeHeader* node) { // When receiving a message, if the IDs match, it's a conflict // Reset to create a new ID. - if (node->id == this->header.id) { + if (node->id == header.id) { Serial.println("Detected an ID conflict."); - this->reset(); + reset(); } // If the message arrives from a higher ID, switch into follower mode - if (node->id > this->header.uplinkId && node->id > this->header.id) { + if (node->id > header.uplinkId && node->id > header.id) { #ifdef RELAY_DEBUGGING // When debugging relay, pretend not to see any nodes above 0x800 if (node->id < 0x800) #endif - this->follow(node); + follow(node); } // If the message arrived from our uplink, track that we're still linked. - if (node->id == this->header.uplinkId) { - this->uplinkTimer.start(UPLINK_TIMEOUT); + if (node->id == header.uplinkId) { + uplinkTimer.start(UPLINK_TIMEOUT); } // If a message indicates that another node is following this one, or // should be (it's not following anything, but this node's ID is higher) // enter or continue re-broadcasting mode. - if (node->uplinkId == this->header.id - || (node->uplinkId == 0 && node->id < this->header.id)) { + if (node->uplinkId == header.id + || (node->uplinkId == 0 && node->id < header.id)) { Serial.printf(" %03X/%03X is following me\n", node->id, node->uplinkId); - this->rebroadcastTimer.start(REBROADCAST_TIME); + rebroadcastTimer.start(REBROADCAST_TIME); } } @@ -212,7 +210,7 @@ class LightNode { NodeMessage* message = (NodeMessage*)data; // Ignore this message if it's the wrong version. - if (message->header.version != this->header.version) { + if (message->header.version != header.version) { #ifdef NODE_DEBUGGING Serial.print(" -- !version "); print_message(message, rssi); @@ -222,18 +220,18 @@ class LightNode { } // Track that another node exists, updating this node's understanding of the mesh. - this->onPeerPing(&message->header); + onPeerPing(&message->header); bool ignore = false; switch (message->recipients) { case RECIPIENTS_ALL: // Ignore this message if not from the uplink - ignore = (message->header.id != this->header.uplinkId); + ignore = (message->header.id != header.uplinkId); break; case RECIPIENTS_ROOT: // Ignore this message if not from one of this node's downlinks - ignore = (message->header.uplinkId != this->header.id); + ignore = (message->header.uplinkId != header.id); break; case RECIPIENTS_INFO: @@ -256,7 +254,7 @@ class LightNode { } // Execute the received command - if (message->recipients != RECIPIENTS_ROOT || !this->is_following()) { + if (message->recipients != RECIPIENTS_ROOT || !is_following()) { Serial.print(" >> "); print_message(message, rssi); Serial.print(" "); @@ -269,7 +267,7 @@ class LightNode { strip.timebase = new_timebase; // Execute the command - auto valid = this->receiver->onCommand( + auto valid = receiver->onCommand( message->command, &message->data ); @@ -280,17 +278,17 @@ class LightNode { } // Re-broadcast the message if appropriate - if (!this->rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { - message->header = this->header; - if (!this->is_following()) + if (!rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { + message->header = header; + if (!is_following()) message->recipients = RECIPIENTS_ALL; - this->broadcast(message, true); + broadcastMessage(message, true); } } - void broadcast(NodeMessage *message, bool is_rebroadcast=false) { + void broadcastMessage(NodeMessage *message, bool is_rebroadcast=false) { // Don't broadcast anything if this node isn't active. - if (this->status != NODE_STATUS_STARTED) + if (status != NODE_STATUS_STARTED) return; message->timebase = strip.timebase + millis(); @@ -321,7 +319,7 @@ class LightNode { message.recipients = RECIPIENTS_INFO; } else if (command == COMMAND_STATE) { message.recipients = RECIPIENTS_ALL; - } else if (this->is_following()) { + } else if (is_following()) { // Follower nodes must request that the root re-sends this message message.recipients = RECIPIENTS_ROOT; } else { @@ -329,16 +327,16 @@ class LightNode { } message.command = command; memcpy(&message.data, data, len); - this->broadcast(&message); + broadcastMessage(&message); } void setup() { #ifdef NODE_DEBUGGING - this->reset(TESTING_NODE_ID); + reset(TESTING_NODE_ID); #else - this->reset(); + reset(); #endif - this->statusTimer.stop(); + statusTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); Serial.println("Mesh: ok"); @@ -346,45 +344,45 @@ class LightNode { void update() { // Check the last time we heard from the uplink node - if (is_following() && this->uplinkTimer.ended()) { - this->follow(NULL); + if (is_following() && uplinkTimer.ended()) { + follow(NULL); } - if (this->statusTimer.every(WIFI_CHECK_RATE)) { + if (statusTimer.every(WIFI_CHECK_RATE)) { // The broadcast timer doubles as a timer for startup delay // Once the initial timer has ended, mark this node as started - if (this->status == NODE_STATUS_STARTING) - this->status = NODE_STATUS_STARTED; + if (status == NODE_STATUS_STARTING) + status = NODE_STATUS_STARTED; // Check WiFi status and update node status if wifi changed if (WiFi.isConnected()) - this->onWifiConnect(); + onWifiConnect(); else - this->onWifiDisconnect(); + onWifiDisconnect(); } } void reset(MeshId id = 0) { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits - this->header.id = id; - this->follow(NULL); + header.id = id; + follow(NULL); } void follow(MeshNodeHeader* node) { if (node == NULL) { - if (this->header.uplinkId != 0) { + if (header.uplinkId != 0) { Serial.println("Uplink lost"); } // Unfollow: following zero means you have no uplink - this->header.uplinkId = 0; - this->onMeshChange(); + header.uplinkId = 0; + onMeshChange(); return; } // Already following? ignore - if (this->header.uplinkId == node->id) + if (header.uplinkId == node->id) return; // Follow @@ -392,12 +390,12 @@ class LightNode { node->id, node->uplinkId ); - this->header.uplinkId = node->id; - this->onMeshChange(); + header.uplinkId = node->id; + onMeshChange(); } bool is_following() { - return this->header.uplinkId != 0; + return header.uplinkId != 0; } }; diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 769c172a19..6daac941f8 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -25,15 +25,11 @@ typedef enum Energy: uint8_t { typedef struct ControlParameters { + ControlParameters(Duration d=MediumDuration, Energy e=Chill) : duration(d), energy(e) {}; + public: Duration duration=MediumDuration; Energy energy=Chill; - - ControlParameters(Duration duration=MediumDuration, Energy energy=Chill) { - this->duration=duration; - this->energy=energy; - }; - } ControlParams; typedef enum PenMode: uint8_t { @@ -72,15 +68,15 @@ typedef enum BeatPulse: uint8_t { class EffectParameters { public: + EffectParameters(EffectMode e=None, PenMode p=Draw, BeatPulse b=Beat, uint8_t c=255) : + effect(e), + pen(p), + beat(b), + chance(c) + { }; + EffectMode effect; PenMode pen=Draw; BeatPulse beat=Beat; uint8_t chance=255; - - EffectParameters(EffectMode effect=None, PenMode pen=Draw, BeatPulse beat=Beat, uint8_t chance=255) { - this->effect=effect; - this->pen=pen; - this->beat=beat; - this->chance=chance; - }; }; diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index 2db9189e61..00ad72a304 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -20,51 +20,53 @@ extern void drawFlash(Particle *particle, WS2812FX* leds); class Particle { public: + Particle(uint16_t pos, CRGB c=CRGB::White, PenMode p=Draw, uint32_t life=20000, ParticleFn fn=drawPoint) : + age(0), + color(c), + brightness(192<<8), + drawFn(fn), + velocity(0), + gravity(0) + { + pen = p; + position = pos; + lifetime = life; + }; + BeatFrame_24_8 born; BeatFrame_24_8 lifetime; BeatFrame_24_8 age; - uint16_t position = 0; - int16_t velocity = 0; - int16_t gravity = 0; + CRGB color; + PenMode pen; + uint16_t brightness; + ParticleFn drawFn; + + uint16_t position; + int16_t velocity; + int16_t gravity; void (*die_fn)(Particle *particle) = NULL; - PenMode pen = Draw; #ifdef PARTICLE_PALETTES CRGBPalette16 palette; // 48 bytes per particle!? #endif - CRGB color; - uint16_t brightness; - ParticleFn drawFn; - - Particle(uint16_t position, CRGB color=CRGB::White, PenMode pen=Draw, uint32_t lifetime=20000, ParticleFn drawFn=drawPoint) - { - this->age = 0; - this->position = position; - this->color = color; - this->pen = pen; - this->lifetime = lifetime; - this->brightness = (192<<8); - this->drawFn = drawFn; - } - void update(BeatFrame_24_8 frame) { // Particles get brighter with the beat - this->brightness = (scale8(particleVolume, 80) + 170) << 8; + brightness = (scale8(particleVolume, 80) + 170) << 8; - this->age = frame - this->born; - this->position = this->udelta16(this->position, this->velocity); - this->velocity = this->delta16(this->velocity, this->gravity); + age = frame - born; + position = udelta16(position, velocity); + velocity = delta16(velocity, gravity); } uint16_t age_frac16(BeatFrame_24_8 age) { - if (age >= this->lifetime) + if (age >= lifetime) return 65535; uint32_t a = age * 65536; - return a / this->lifetime; + return a / lifetime; } uint16_t udelta16(uint16_t x, int16_t dx) @@ -88,17 +90,17 @@ class Particle { CRGB color_at(uint16_t age_frac) { // Particles get dimmer with age uint8_t a = age_frac >> 8; - uint8_t brightness = scale8((uint8_t)(this->brightness>>8), 255-a); + uint8_t brightness = scale8((uint8_t)(brightness>>8), 255-a); #ifdef PARTICLE_PALETTES // a black pattern actually means to use the current palette - if (this->color == CRGB(0,0,0)) - return ColorFromPalette(this->palette, a, brightness); + if (color == CRGB(0,0,0)) + return ColorFromPalette(palette, a, brightness); #endif - uint8_t r = scale8(this->color.r, brightness); - uint8_t g = scale8(this->color.g, brightness); - uint8_t b = scale8(this->color.b, brightness); + uint8_t r = scale8(color.r, brightness); + uint8_t g = scale8(color.g, brightness); + uint8_t b = scale8(color.b, brightness); return CRGB(r,g,b); } @@ -106,7 +108,7 @@ class Particle { CRGB c = CRGB(strip.getPixelColor(pos)); CRGB new_color; - switch (this->pen) { + switch (pen) { case Draw: new_color = color; break; diff --git a/usermods/Tubes/sound.h b/usermods/Tubes/sound.h index 02280170c5..facd751577 100644 --- a/usermods/Tubes/sound.h +++ b/usermods/Tubes/sound.h @@ -14,30 +14,30 @@ class Sounder { } void update() { - if (!this->active) { + if (!active) { particleVolume = DEFAULT_PARTICLE_VOLUME; // Average volume return; } um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - this->active = false; - this->overlay = false; + active = false; + overlay = false; return; } float volumeSmth = *(float*)um_data->u_data[0]; - this->volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. - particleVolume = this->volume; + volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. + particleVolume = volume; } void handleOverlayDraw() { - if (!this->active) + if (!active) return; - if (!this->overlay) + if (!overlay) return; - int len = scale8(this->volume, 32); + int len = scale8(volume, 32); Segment& segment = strip.getMainSegment(); diff --git a/usermods/Tubes/timer.h b/usermods/Tubes/timer.h index 22b00ab482..87b5de71b3 100644 --- a/usermods/Tubes/timer.h +++ b/usermods/Tubes/timer.h @@ -11,19 +11,19 @@ class GlobalTimer { void setup() { - this->last_millis = this->now_millis = millis(); - this->last_micros = this->now_micros = micros(); + last_millis = now_millis = millis(); + last_micros = now_micros = micros(); } void update() { - this->last_millis = this->now_millis; - this->now_millis = millis(); - this->delta_millis = this->now_millis - this->last_millis; + last_millis = now_millis; + now_millis = millis(); + delta_millis = now_millis - last_millis; - this->last_micros = this->now_micros; - this->now_micros = micros(); - this->delta_micros = this->now_micros - this->last_micros; + last_micros = now_micros; + now_micros = micros(); + delta_micros = now_micros - last_micros; } }; @@ -36,32 +36,32 @@ class Timer { uint32_t markTime; void start(uint32_t duration_ms) { - this->markTime = globalTimer.now_millis + duration_ms; + markTime = globalTimer.now_millis + duration_ms; } void stop() { - this->start(0); + start(0); } uint32_t since_mark() { - if (globalTimer.now_millis < this->markTime) + if (globalTimer.now_millis < markTime) return 0; - return globalTimer.now_millis - this->markTime; + return globalTimer.now_millis - markTime; } void snooze(uint32_t duration_ms) { - while (this->markTime < globalTimer.now_millis) - this->markTime += duration_ms; + while (markTime < globalTimer.now_millis) + markTime += duration_ms; } bool ended() { - return globalTimer.now_millis > this->markTime; + return globalTimer.now_millis > markTime; } bool every(uint32_t duration_ms) { - if (!this->ended()) + if (!ended()) return 0; - this->snooze(duration_ms); + snooze(duration_ms); return 1; } }; \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index e227610de0..c72ca2972f 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -62,102 +62,101 @@ class VirtualStrip { bool beat_pulse; int bps = 0; - VirtualStrip(uint8_t num_leds) + VirtualStrip(uint8_t num) : num_leds(num) { - this->fade = Dead; - this->num_leds = num_leds; + fade = Dead; } void load(Background &background, uint8_t fade_speed=DEFAULT_FADE_SPEED) { - this->background = background; - this->fade = FadeIn; - this->fader = 0; - this->fade_speed = fade_speed; - this->brightness = DEF_BRIGHT; + background = background; + fade = FadeIn; + fader = 0; + fade_speed = fade_speed; + brightness = DEF_BRIGHT; } bool isWled() { - return this->background.wled_fx_id != 0; + return background.wled_fx_id != 0; } void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) { - if (this->fade == Dead) + if (fade == Dead) return; - this->fade = FadeOut; - this->fade_speed = fade_speed; + fade = FadeOut; + fade_speed = fade_speed; } void darken(uint8_t amount=10) { - fadeToBlackBy( this->leds, this->num_leds, amount); + fadeToBlackBy( leds, num_leds, amount); } void fill(CRGB crgb) { - fill_solid( this->leds, this->num_leds, crgb); + fill_solid( leds, num_leds, crgb); } void update(BeatFrame_24_8 frame, uint8_t beat_pulse) { - if (this->fade == Dead) + if (fade == Dead) return; - this->frame = frame; + frame = frame; - switch (this->background.sync) { + switch (background.sync) { case All: break; case SinDrift: // Drift slightly - this->frame = frame + (beatsin16( 5 ) >> 6); + frame = frame + (beatsin16( 5 ) >> 6); break; case Swing: // Swing the beat - this->frame = swing(frame); + frame = swing(frame); break; case SwingDrift: // Swing the beat AND drift slightly - this->frame = swing(frame) + (beatsin16( 5 ) >> 6); + frame = swing(frame) + (beatsin16( 5 ) >> 6); break; case Pulse: // Pulsing from 30 - 210 brightness - this->brightness = scale8(beatsin8( 10 ), 180) + 30; + brightness = scale8(beatsin8( 10 ), 180) + 30; break; } - this->hue = (this->frame >> 4) % 256; - this->beat = (this->frame >> 8) % 16; - this->beat_pulse = beat_pulse; + hue = (frame >> 4) % 256; + beat = (frame >> 8) % 16; + beat_pulse = beat_pulse; // Animate this virtual strip - this->background.animate(this); + background.animate(this); - switch (this->fade) { + switch (fade) { case Steady: case Dead: break; case FadeIn: - if (65535 - this->fader < this->fade_speed) { - this->fader = 65535; - this->fade = Steady; + if (65535 - fader < fade_speed) { + fader = 65535; + fade = Steady; } else { - this->fader += this->fade_speed; + fader += fade_speed; } break; case FadeOut: - if (this->fader < this->fade_speed) { - this->fader = 0; - this->fade = Dead; + if (fader < fade_speed) { + fader = 0; + fade = Dead; return; } else { - this->fader -= this->fade_speed; + fader -= fade_speed; } break; } @@ -170,25 +169,25 @@ class VirtualStrip { } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { - return CHSV(this->hue + offset, saturation, value); + return CHSV(hue + offset, saturation, value); } void blend(CRGB output[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { - if (this->fade == Dead) + if (fade == Dead) return; // WLED is blended in elsewhere - if (this->isWled()) + if (isWled()) return; - brightness = scale8(this->brightness, brightness); + brightness = scale8(brightness, brightness); for (unsigned i=0; i < num_leds; i++) { uint8_t pos = i; - CRGB c = this->leds[pos]; + CRGB c = leds[pos]; nscale8x3(c.r, c.g, c.b, brightness); - nscale8x3(c.r, c.g, c.b, this->fader>>8); + nscale8x3(c.r, c.g, c.b, fader>>8); if (overwrite) output[i] = c; else @@ -198,12 +197,12 @@ class VirtualStrip { uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) { - return scaled16to8(sin16( this->frame << 7 ) + 32768, lowest, highest); + return scaled16to8(sin16( frame << 7 ) + 32768, lowest, highest); } uint8_t bpm_cos16( uint16_t lowest=0, uint16_t highest=65535 ) { - return scaled16to8(cos16( this->frame << 7 ) + 32768, lowest, highest); + return scaled16to8(cos16( frame << 7 ) + 32768, lowest, highest); } }; From 6ca660a59773e2fb93a208171eb9f649f475ea84 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 13 Aug 2023 22:20:08 -0400 Subject: [PATCH 189/263] Register Dig Unos (and Dig2Go) --- platformio.ini | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/platformio.ini b/platformio.ini index 29f1ced751..54a53eb682 100644 --- a/platformio.ini +++ b/platformio.ini @@ -399,6 +399,16 @@ lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +[env:esp32_quinled_diguno] +extends = env:esp32dev +build_flags = ${env:esp32dev.build_flags} -D RLYPIN=12 -D BTNPIN=0 -D DATA_PINS=16 -D DMTYPE=1 -D I2S_SDPIN=19 -D I2S_WSPIN=4 -D I2S_CKPIN=18 + -D USERMOD_AUDIOREACTIVE +lib_deps = ${env:esp32dev.lib_deps} + https://github.com/blazoncek/arduinoFFT.git +upload_speed = 690000 +board_build.f_flash = 80000000L +board_build.flash_mode = qio + [env:esp32dev_qio80] board = esp32dev platform = ${esp32.platform} From 887dc9625d140c9c92c85abd90b354252099366c Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 15 Aug 2023 01:27:42 -0700 Subject: [PATCH 190/263] Fix mistaken refactor that was crashing --- usermods/Tubes/virtual_strip.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index c72ca2972f..2c8fa72e8e 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -67,12 +67,12 @@ class VirtualStrip { fade = Dead; } - void load(Background &background, uint8_t fade_speed=DEFAULT_FADE_SPEED) + void load(Background &b, uint8_t fs=DEFAULT_FADE_SPEED) { - background = background; + background = b; fade = FadeIn; fader = 0; - fade_speed = fade_speed; + fade_speed = fs; brightness = DEF_BRIGHT; } @@ -80,12 +80,12 @@ class VirtualStrip { return background.wled_fx_id != 0; } - void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) + void fadeOut(uint8_t fs=DEFAULT_FADE_SPEED) { if (fade == Dead) return; fade = FadeOut; - fade_speed = fade_speed; + fade_speed = fs; } void darken(uint8_t amount=10) @@ -98,14 +98,14 @@ class VirtualStrip { fill_solid( leds, num_leds, crgb); } - void update(BeatFrame_24_8 frame, uint8_t beat_pulse) + void update(BeatFrame_24_8 fr, uint8_t bp) { if (fade == Dead) return; - frame = frame; + frame = fr; - switch (background.sync) { + switch (this->background.sync) { case All: break; @@ -131,7 +131,7 @@ class VirtualStrip { } hue = (frame >> 4) % 256; beat = (frame >> 8) % 16; - beat_pulse = beat_pulse; + beat_pulse = bp; // Animate this virtual strip background.animate(this); @@ -172,7 +172,7 @@ class VirtualStrip { return CHSV(hue + offset, saturation, value); } - void blend(CRGB output[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { + void blend(CRGB output[], uint8_t num_leds, uint8_t br, bool overwrite=0) { if (fade == Dead) return; @@ -180,7 +180,7 @@ class VirtualStrip { if (isWled()) return; - brightness = scale8(brightness, brightness); + br = scale8(brightness, br); for (unsigned i=0; i < num_leds; i++) { uint8_t pos = i; From 36bd5b3aba2ff063e5089679b931b2a2a3b35f58 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 15 Aug 2023 02:04:34 -0700 Subject: [PATCH 191/263] fix bug due to refactor --- usermods/Tubes/beats.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h index 52924b8429..e3d6489d5b 100644 --- a/usermods/Tubes/beats.h +++ b/usermods/Tubes/beats.h @@ -34,10 +34,10 @@ class BeatController { } } - void sync(accum88 bpm, BeatFrame_24_8 frac) { + void sync(accum88 b, BeatFrame_24_8 f) { accum88 last_bpm = bpm; - bpm = bpm; - frac = frac; + bpm = b; + frac = f; accum = 0; micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); @@ -46,12 +46,12 @@ class BeatController { print_bpm(); } - void set_bpm(accum88 bpm) { - sync(bpm, frac); + void set_bpm(accum88 b) { + sync(b, frac); } - void adjust_bpm(saccum78 bpm) { - sync(bpm + bpm, frac); + void adjust_bpm(saccum78 b) { + sync(bpm + b, frac); } void start_phrase() { From daea6d389833ec5050d6d585a9d12cd536af300d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 15 Aug 2023 02:57:14 -0700 Subject: [PATCH 192/263] Fix more errors introduced by refactor --- usermods/Tubes/controller.h | 12 ++++++------ usermods/Tubes/master.h | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b3fcda3b3e..b817aebb77 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -76,8 +76,8 @@ class Button { uint8_t pin; bool lastPressed = false; - void setup(uint8_t pin) { - pin = pin; + void setup(uint8_t p) { + pin = p; pinMode(pin, INPUT_PULLUP); debounceTimer.start(0); } @@ -797,9 +797,9 @@ class PatternController : public MessageReceiver { setPowerSave(!power_save); } - void setPowerSave(bool power_save) { + void setPowerSave(bool ps) { + power_save = ps; Serial.printf("power_save: %d\n", power_save); - power_save = power_save; // Remember this setting on the next boot EEPROM.begin(2560); @@ -814,8 +814,8 @@ class PatternController : public MessageReceiver { EEPROM.end(); } - void setRole(ControllerRole role) { - role = role; + void setRole(ControllerRole r) { + role = r; Serial.printf("Role = %d", role); EEPROM.begin(2560); EEPROM.write(ROLE_EEPROM_LOCATION, role); diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 5549076d0e..3ae0b2303a 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -71,36 +71,36 @@ class Master { addFlash(CRGB::Red); } - void onButtonPress(uint8_t button) { - if (button == 0) + void onButtonPress(uint8_t b) { + if (b == 0) return; - if (button == 4) { + if (b == 4) { Serial.println((char *)F("Skip >>")); controller->force_next(); ok(); return; } - if (button == 3) { + if (b == 3) { tap(); return; } Serial.print((char *)F("Pressed ")); - Serial.println(button); + Serial.println(b); } - void onButtonRelease(uint8_t button) { + void onButtonRelease(uint8_t b) { #ifdef EXTRA_STUFF - if (button == 2) { + if (b == 2) { if (palette_mode) controller->_load_palette(palette_id); palette_mode = false; } #endif - if (button == 3) { + if (b == 3) { if (taps == 0) return; tap(); @@ -108,7 +108,7 @@ class Master { } Serial.print((char *)F("Released ")); - Serial.println(button); + Serial.println(b); } void tap() { From 2e940eeee85af08fee44fa4a0f42ac2b825ec021 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 16 Aug 2023 02:48:43 -0700 Subject: [PATCH 193/263] Try to not do multiple changes at the same time, to prevent a jumpy transition --- usermods/Tubes/controller.h | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b817aebb77..e8cf39e815 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -240,6 +240,14 @@ class PatternController : public MessageReceiver { #endif load_pattern(next_state); next_state.pattern_phrase = phrase + set_next_pattern(phrase); + + // Don't change pattern and others at the same time + if (next_state.pattern_phrase == next_state.palette_phrase) { + next_state.palette_phrase += random8(1,3); + } + if (next_state.pattern_phrase == next_state.effect_phrase) { + next_state.palette_phrase += random8(1,3); + } changed = true; } if (phrase >= next_state.palette_phrase) { @@ -248,6 +256,14 @@ class PatternController : public MessageReceiver { #endif load_palette(next_state); next_state.palette_phrase = phrase + set_next_palette(phrase); + + // Don't change palette and others at the same time + if (next_state.palette_phrase == next_state.pattern_phrase) { + next_state.palette_phrase += random8(1,3); + } + if (next_state.palette_phrase == next_state.effect_phrase) { + next_state.palette_phrase += random8(1,3); + } changed = true; } if (phrase >= next_state.effect_phrase) { @@ -256,16 +272,18 @@ class PatternController : public MessageReceiver { #endif load_effect(next_state); next_state.effect_phrase = phrase + set_next_effect(phrase); + + // Don't change palette and others at the same time + if (next_state.effect_phrase == next_state.pattern_phrase) { + next_state.effect_phrase += random8(1,3); + } + if (next_state.effect_phrase == next_state.palette_phrase) { + next_state.effect_phrase += random8(1,3); + } changed = true; } if (changed) { - // For now, WLED doesn't handle transitioning pattern & palette well. - // Stagger them - if (next_state.pattern_phrase == next_state.palette_phrase) { - next_state.palette_phrase += random8(1,3); - } - next_state.print(); Serial.println(); } From 1f4cf27716778caa84e9b3d30566f1e5bc3bca0d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 16 Aug 2023 03:31:54 -0700 Subject: [PATCH 194/263] Fix the previous fix --- usermods/Tubes/controller.h | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index e8cf39e815..da1137cb19 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -242,11 +242,8 @@ class PatternController : public MessageReceiver { next_state.pattern_phrase = phrase + set_next_pattern(phrase); // Don't change pattern and others at the same time - if (next_state.pattern_phrase == next_state.palette_phrase) { - next_state.palette_phrase += random8(1,3); - } - if (next_state.pattern_phrase == next_state.effect_phrase) { - next_state.palette_phrase += random8(1,3); + while (next_state.pattern_phrase == next_state.palette_phrase || next_state.pattern_phrase == next_state.effect_phrase) { + next_state.pattern_phrase += random8(1,3); } changed = true; } @@ -258,11 +255,8 @@ class PatternController : public MessageReceiver { next_state.palette_phrase = phrase + set_next_palette(phrase); // Don't change palette and others at the same time - if (next_state.palette_phrase == next_state.pattern_phrase) { - next_state.palette_phrase += random8(1,3); - } - if (next_state.palette_phrase == next_state.effect_phrase) { - next_state.palette_phrase += random8(1,3); + while (next_state.palette_phrase == next_state.pattern_phrase || next_state.palette_phrase == next_state.effect_phrase) { + next_state.palette_phrase += random8(1,3); } changed = true; } @@ -274,11 +268,8 @@ class PatternController : public MessageReceiver { next_state.effect_phrase = phrase + set_next_effect(phrase); // Don't change palette and others at the same time - if (next_state.effect_phrase == next_state.pattern_phrase) { - next_state.effect_phrase += random8(1,3); - } - if (next_state.effect_phrase == next_state.palette_phrase) { - next_state.effect_phrase += random8(1,3); + while (next_state.effect_phrase == next_state.pattern_phrase || next_state.effect_phrase == next_state.palette_phrase) { + next_state.effect_phrase += random8(1,3); } changed = true; } From bc6e438e11b2ed453969e151486022166c80602e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 22 Aug 2023 22:32:14 -0700 Subject: [PATCH 195/263] Fix issues that could cause crashes if flashed with wrong version --- usermods/Tubes/beats.h | 5 ++ usermods/Tubes/controller.h | 79 +++++++++++++++++++++++++++--- usermods/Tubes/default_config.json | 2 +- usermods/Tubes/firmware.sh | 4 +- usermods/Tubes/node.h | 35 ++++++++++++- 5 files changed, 113 insertions(+), 12 deletions(-) diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h index e3d6489d5b..c60e6b1b5e 100644 --- a/usermods/Tubes/beats.h +++ b/usermods/Tubes/beats.h @@ -35,6 +35,11 @@ class BeatController { } void sync(accum88 b, BeatFrame_24_8 f) { + if (b < 40<<8) { + // Reject BPMs that are too low. + return; + } + accum88 last_bpm = bpm; bpm = b; frac = f; diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index da1137cb19..1e97cb4ea1 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,6 +15,8 @@ #include "global_state.h" #include "node.h" +#define EEPSIZE 2560 + const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; @@ -26,7 +28,7 @@ const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; #define MAX_COLOR_CHANGE_PHRASES 10 #define ROLE_EEPROM_LOCATION 2559 -#define BOOT_OPTIONS_EEPROM_LOCATION 2557 +#define BOOT_OPTIONS_EEPROM_LOCATION 2551 #define IDENTIFY_STUCK_PATTERNS #define IDENTIFY_STUCK_PALETTES @@ -169,7 +171,7 @@ class PatternController : public MessageReceiver { void setup() { node->setup(); - EEPROM.begin(2560); + EEPROM.begin(EEPSIZE); role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); if (role == 255) { role = UnknownRole; @@ -784,10 +786,6 @@ class PatternController : public MessageReceiver { stateUpdated(CALL_MODE_DIRECT_CHANGE); } - static void set_wled_brightness(uint8_t brightness) { - strip.setBrightness(brightness); - } - void setBrightness(uint8_t brightness) { Serial.printf("brightness: %d\n", brightness); @@ -826,7 +824,7 @@ class PatternController : public MessageReceiver { void setRole(ControllerRole r) { role = r; Serial.printf("Role = %d", role); - EEPROM.begin(2560); + EEPROM.begin(EEPSIZE); EEPROM.write(ROLE_EEPROM_LOCATION, role); EEPROM.write(BOOT_OPTIONS_EEPROM_LOCATION, 0); // Reset all boot options EEPROM.end(); @@ -1135,6 +1133,9 @@ class PatternController : public MessageReceiver { } void broadcast_options() { + if (!node->is_following()) { + load_options(options); + } node->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } @@ -1282,4 +1283,66 @@ class PatternController : public MessageReceiver { } } -}; +#define WIZMOTE_BUTTON_ON 1 +#define WIZMOTE_BUTTON_OFF 2 +#define WIZMOTE_BUTTON_NIGHT 3 +#define WIZMOTE_BUTTON_ONE 16 +#define WIZMOTE_BUTTON_TWO 17 +#define WIZMOTE_BUTTON_THREE 18 +#define WIZMOTE_BUTTON_FOUR 19 +#define WIZMOTE_BUTTON_BRIGHT_UP 9 +#define WIZMOTE_BUTTON_BRIGHT_DOWN 8 + + virtual bool onButton(uint8_t button_id) { + Serial.printf("Button %d\n", button_id); + switch (button_id) { + case WIZMOTE_BUTTON_ON: + WLED::instance().initAP(true); + acknowledge(); + return true; + + case WIZMOTE_BUTTON_OFF: + WiFi.softAPdisconnect(true); + acknowledge(); + return true; + + case WIZMOTE_BUTTON_ONE: + // Make it interesting - switch to a good pattern and timesync mode + acknowledge(); + return true; + + case WIZMOTE_BUTTON_FOUR: + // Turn on or off debugging + setDebugging(!options.debugging); + acknowledge(); + return true; + + case WIZMOTE_BUTTON_BRIGHT_UP: + if (options.brightness <= 230) + setBrightness(options.brightness + 25); + return true; + + case WIZMOTE_BUTTON_BRIGHT_DOWN: + if (options.brightness >= 25) + setBrightness(options.brightness - 25); + return true; + + case WIZMOTE_BUTTON_NIGHT: + // Turn off debugging and wifi + WiFi.softAPdisconnect(true); + setDebugging(false); + acknowledge(); + return true; + + + // case WIZMOTE_BUTTON_ON : setOn(); stateUpdated(CALL_MODE_BUTTON); break; + // case WIZMOTE_BUTTON_OFF : setOff(); stateUpdated(CALL_MODE_BUTTON); break; + // case WIZMOTE_BUTTON_TWO : presetWithFallback(2, FX_MODE_BREATH, 0); resetNightMode(); break; + // case WIZMOTE_BUTTON_THREE : presetWithFallback(3, FX_MODE_FIRE_FLICKER, 0); resetNightMode(); break; + default: + return false; + } + } + + +}; \ No newline at end of file diff --git a/usermods/Tubes/default_config.json b/usermods/Tubes/default_config.json index 5676f56cf3..d32eb86079 100644 --- a/usermods/Tubes/default_config.json +++ b/usermods/Tubes/default_config.json @@ -1 +1 @@ -{"rev":[1,0],"vid":2307130,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"Tube","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":1000,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0,"freq":0}]},"com":[],"btn":{"max":4,"pull":true,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8,"val":2.8},"tr":{"mode":true,"dur":80,"pal":1,"rpc":5},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"grp":1,"ret":0}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"e131prio":0,"addr":1,"dss":0,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0],"p":0},"hue":{"en":false,"id":1,"iv":25,"recv":{"on":true,"bri":true,"col":true},"ip":[0,0,0,0]},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"remote":{"remote_enabled":false,"linked_remote":""},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":false,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1]},"config":{"squelch":10,"gain":60,"AGC":1},"dynamics":{"limiter":true,"rise":80,"fall":1400},"frequency":{"scale":3},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file +{"rev":[1,0],"vid":2308110,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"WLED-AP","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":700,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0,"freq":0}]},"com":[],"btn":{"max":4,"pull":true,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8,"val":2.8},"tr":{"mode":true,"dur":80,"pal":1,"rpc":5},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"grp":1,"ret":0}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"e131prio":0,"addr":1,"dss":0,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0],"p":0},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"remote":{"remote_enabled":false,"linked_remote":""},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":true,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1]},"config":{"squelch":10,"gain":60,"AGC":1},"dynamics":{"limiter":true,"rise":80,"fall":1400},"frequency":{"scale":3},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index b8b5d81698..e92d0b4a13 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -10,7 +10,7 @@ upload_firmware() { echo "Uploading firmware" sftp control@brcac.com </dev/null + curl -s -F "update=@../../build_output/firmware/tubes.bin" $1/update >/dev/null echo "Updated; wait..." sleep 5 update_config $1 diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index dfdc9d3536..fcb3717be7 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -79,6 +79,10 @@ class MessageReceiver { // Abstract: subclasses must define return false; } + virtual bool onButton(uint8_t button_id) { + // Abstract: subclasses must define + return false; + } }; typedef enum{ @@ -399,8 +403,37 @@ class LightNode { } }; +typedef struct wizmote_message { + uint8_t program; // 0x91 for ON button, 0x81 for all others + uint8_t seq[4]; // Incremetal sequence number 32 bit unsigned integer LSB first + uint8_t byte5 = 32; // Unknown + uint8_t button; // Identifies which button is being pressed + uint8_t byte8 = 1; // Unknown, but always 0x01 + uint8_t byte9 = 100; // Unnkown, but always 0x64 + + uint8_t byte10; // Unknown, maybe checksum + uint8_t byte11; // Unknown, maybe checksum + uint8_t byte12; // Unknown, maybe checksum + uint8_t byte13; // Unknown, maybe checksum +} wizmote_message; + +void onWizmote(uint8_t* address, wizmote_message* data, uint8_t len) { + // First make sure this is a WizMote message. + if (len != sizeof(wizmote_message) || data->byte8 != 1 || data->byte9 != 100 || data->byte5 != 32) + return; + + static uint32_t last_seq = 0; + uint32_t cur_seq = data->seq[0] | (data->seq[1] << 8) | (data->seq[2] << 16) | (data->seq[3] << 24); + if (cur_seq == last_seq) + return; + last_seq = cur_seq; + + LightNode::instance->receiver->onButton(data->button); +} + LightNode* LightNode::instance = nullptr; -void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { +void onDataReceived(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { LightNode::instance->onPeerData(address, data, len, rssi, broadcast); + onWizmote(address, (wizmote_message*)data, len); } From 77b3b8956805ef1fc0e9acdab3cb98939e2ea04a Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 23 Aug 2023 09:39:50 -0700 Subject: [PATCH 196/263] Make the powersave option persistent --- usermods/Tubes/controller.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 1e97cb4ea1..3339cbaaea 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -176,11 +176,12 @@ class PatternController : public MessageReceiver { if (role == 255) { role = UnknownRole; } - EEPROM.end(); Serial.printf("Role = %d\n", role); auto b = EEPROM.read(BOOT_OPTIONS_EEPROM_LOCATION); Serial.printf("EEPROM read: %d\n", b); + EEPROM.end(); + BootOptions* boot = (BootOptions*)&b; switch (boot->default_power_save) { case BOOT_OPTION_POWER_SAVE_OFF: From cedcf65c7deeab997fbbee2559504d82e0fc1a8f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 23 Aug 2023 10:55:46 -0700 Subject: [PATCH 197/263] Wizmote controls --- usermods/Tubes/Tubes.h | 2 +- usermods/Tubes/controller.h | 103 +++++++++++++++++++++++++++--------- usermods/Tubes/debug.h | 2 +- 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 6b43794151..8198b808f7 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -56,7 +56,7 @@ class TubesUsermod : public Usermod { globalTimer.setup(); beats.setup(); controller.setup(); - if (controller.isMaster()) { + if (controller.isMasterRole()) { master = new Master(&controller); master->setup(); } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3339cbaaea..c402859245 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -164,7 +164,7 @@ class PatternController : public MessageReceiver { } } - bool isMaster() { + bool isMasterRole() { return role >= MasterRole; } @@ -576,7 +576,9 @@ class PatternController : public MessageReceiver { } void load_options(ControllerOptions &options) { - strip.setBrightness(options.brightness); + // Power-saving devices retain their WLED brightness + if (!power_save) + strip.setBrightness(options.brightness); } void load_pattern(TubeState &tube_state) { @@ -791,14 +793,22 @@ class PatternController : public MessageReceiver { Serial.printf("brightness: %d\n", brightness); options.brightness = brightness; - broadcast_options(); + load_options(options); + + // The master controls all followers + if (!node->is_following()) + broadcast_options(); } void setDebugging(bool debugging) { Serial.printf("debugging: %d\n", debugging); options.debugging = debugging; - broadcast_options(); + load_options(options); + + // The master controls all followers + if (!node->is_following()) + broadcast_options(); } void togglePowerSave() { @@ -1134,9 +1144,6 @@ class PatternController : public MessageReceiver { } void broadcast_options() { - if (!node->is_following()) { - load_options(options); - } node->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } @@ -1194,7 +1201,7 @@ class PatternController : public MessageReceiver { case COMMAND_BEATS: // the master control ignores this request, it has its own // beat measuring. - if (isMaster()) + if (isMasterRole()) return false; set_tapped_bpm(*(accum88*)data, 0); return true; @@ -1295,52 +1302,98 @@ class PatternController : public MessageReceiver { #define WIZMOTE_BUTTON_BRIGHT_DOWN 8 virtual bool onButton(uint8_t button_id) { - Serial.printf("Button %d\n", button_id); + bool isMaster = !this->node->is_following(); + switch (button_id) { case WIZMOTE_BUTTON_ON: WLED::instance().initAP(true); + setDebugging(true); acknowledge(); return true; case WIZMOTE_BUTTON_OFF: WiFi.softAPdisconnect(true); + WiFi.disconnect(false, true); + apActive = false; + apBehavior = AP_BEHAVIOR_BUTTON_ONLY; + setDebugging(false); acknowledge(); return true; case WIZMOTE_BUTTON_ONE: - // Make it interesting - switch to a good pattern and timesync mode - acknowledge(); + // Make it interesting - switch to a good pattern and sync mode + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote preset 1: de-sync"); + + while (next_state.pattern_sync_id == All) + set_next_pattern(0); + + this->force_next(); + return true; + + case WIZMOTE_BUTTON_TWO: + // Apply an interesting effect & sync layer + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote preset 2: add an effect"); + + while (next_state.effect_params.effect == None) + set_next_effect(0); + + this->force_next(); + return true; + + case WIZMOTE_BUTTON_THREE: + // Turn on flames + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote preset 3: flames!"); + next_state.pattern_id = 63; // Fire + next_state.pattern_sync_id = SyncMode::All; + + this->force_next(); return true; case WIZMOTE_BUTTON_FOUR: - // Turn on or off debugging - setDebugging(!options.debugging); - acknowledge(); + // Make it an interesting combo + // Only the master will respond to this + if (!isMaster) + return false; + + // 38: Noise 3 + Serial.println("WizMote preset 4: interesting pattern"); + set_next_pattern(0); + next_state.pattern_id = 38; // overwrite with: Noise 3 + + this->force_next(); return true; case WIZMOTE_BUTTON_BRIGHT_UP: + // Brighten (ignored if in power save mode) + Serial.println("WizMote: brightness up"); if (options.brightness <= 230) setBrightness(options.brightness + 25); return true; case WIZMOTE_BUTTON_BRIGHT_DOWN: + // Dim (ignored if in power save mode) + Serial.println("WizMote: brightness down"); + if (options.brightness >= 25) setBrightness(options.brightness - 25); return true; - case WIZMOTE_BUTTON_NIGHT: - // Turn off debugging and wifi - WiFi.softAPdisconnect(true); - setDebugging(false); - acknowledge(); - return true; - + // case WIZMOTE_BUTTON_NIGHT: - // case WIZMOTE_BUTTON_ON : setOn(); stateUpdated(CALL_MODE_BUTTON); break; - // case WIZMOTE_BUTTON_OFF : setOff(); stateUpdated(CALL_MODE_BUTTON); break; - // case WIZMOTE_BUTTON_TWO : presetWithFallback(2, FX_MODE_BREATH, 0); resetNightMode(); break; - // case WIZMOTE_BUTTON_THREE : presetWithFallback(3, FX_MODE_FIRE_FLICKER, 0); resetNightMode(); break; default: + Serial.printf("Button %d master=%d\n", button_id, isMaster); return false; } } diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c6870f444c..0d2a895fda 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -77,7 +77,7 @@ class DebugController { ); Serial.printf("=== Controller: "); - if (controller->isMaster()) { + if (controller->isMasterRole()) { Serial.print("PRIMARY "); } if (controller->sound.active) { From ee3c8d4046b4e55bc64453f0fbf881aa7bdf66bf Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 23 Aug 2023 11:28:07 -0700 Subject: [PATCH 198/263] Release version 12 --- usermods/Tubes/updater.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 43923a19e4..cf2ccc9a66 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 11 +#define RELEASE_VERSION 12 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { From 77e85804603a0afce0a15dce9c317aaf7a8ec61e Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 24 Aug 2023 00:37:28 -0700 Subject: [PATCH 199/263] Fix the wizmote presets to work more reliably --- usermods/Tubes/controller.h | 50 +++++++++++++++++++++++++++++-------- usermods/Tubes/firmware.sh | 7 +++--- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c402859245..514d2a9f0d 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -30,8 +30,8 @@ const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; #define ROLE_EEPROM_LOCATION 2559 #define BOOT_OPTIONS_EEPROM_LOCATION 2551 -#define IDENTIFY_STUCK_PATTERNS -#define IDENTIFY_STUCK_PALETTES +// #define IDENTIFY_STUCK_PATTERNS +// #define IDENTIFY_STUCK_PALETTES typedef struct { bool debugging; @@ -1301,6 +1301,18 @@ class PatternController : public MessageReceiver { #define WIZMOTE_BUTTON_BRIGHT_UP 9 #define WIZMOTE_BUTTON_BRIGHT_DOWN 8 + void force_next_pattern() { + next_state.pattern_phrase = current_state.beat_frame >> 12; + if (next_state.palette_phrase == next_state.pattern_phrase) + next_state.palette_phrase += random8(0, 5); + force_next(); + } + + void force_next_effect() { + next_state.effect_phrase = current_state.beat_frame >> 12; + force_next(); + } + virtual bool onButton(uint8_t button_id) { bool isMaster = !this->node->is_following(); @@ -1313,8 +1325,9 @@ class PatternController : public MessageReceiver { case WIZMOTE_BUTTON_OFF: WiFi.softAPdisconnect(true); - WiFi.disconnect(false, true); apActive = false; + WiFi.disconnect(false, true); + WLED::instance().enableWatchdog(); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; setDebugging(false); acknowledge(); @@ -1328,10 +1341,11 @@ class PatternController : public MessageReceiver { Serial.println("WizMote preset 1: de-sync"); + set_next_pattern(0); while (next_state.pattern_sync_id == All) set_next_pattern(0); - this->force_next(); + this->force_next_pattern(); return true; case WIZMOTE_BUTTON_TWO: @@ -1342,23 +1356,26 @@ class PatternController : public MessageReceiver { Serial.println("WizMote preset 2: add an effect"); + set_next_effect(0); while (next_state.effect_params.effect == None) set_next_effect(0); - this->force_next(); + this->force_next_effect(); return true; case WIZMOTE_BUTTON_THREE: - // Turn on flames + // Turn on flames. Also up the tempo to 125 // Only the master will respond to this if (!isMaster) return false; + // Switch to house mode + set_tapped_bpm(125<<8); + Serial.println("WizMote preset 3: flames!"); next_state.pattern_id = 63; // Fire next_state.pattern_sync_id = SyncMode::All; - - this->force_next(); + this->force_next_pattern(); return true; case WIZMOTE_BUTTON_FOUR: @@ -1369,10 +1386,11 @@ class PatternController : public MessageReceiver { // 38: Noise 3 Serial.println("WizMote preset 4: interesting pattern"); + set_next_pattern(0); next_state.pattern_id = 38; // overwrite with: Noise 3 - this->force_next(); + this->force_next_pattern(); return true; case WIZMOTE_BUTTON_BRIGHT_UP: @@ -1390,7 +1408,19 @@ class PatternController : public MessageReceiver { setBrightness(options.brightness - 25); return true; - // case WIZMOTE_BUTTON_NIGHT: + case WIZMOTE_BUTTON_NIGHT: + // Chill mode + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote: chill"); + + // Switch to deep house mode + set_tapped_bpm(120<<8); + + this->force_next(); + return true; default: Serial.printf("Button %d master=%d\n", button_id, isMaster); diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index e92d0b4a13..4f811552aa 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -20,13 +20,14 @@ update_config() { # return; echo "Updating configuration via OTA" - curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" >/dev/null - curl -s http://$1/reset >/dev/null + curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" -H "Connection: close" --no-keepalive + echo "Configured; wait..." + curl -s http://$1/reset -H "Connection: close" >/dev/null } update_firmware() { echo "Updating firmware via OTA" - curl -s -F "update=@../../build_output/firmware/tubes.bin" $1/update >/dev/null + curl -s -F "update=@../../build_output/firmware/tubes.bin" -H "Connection: close" --no-keepalive $1/update >/dev/null echo "Updated; wait..." sleep 5 update_config $1 From fbcac467f58156d4405378f18eccd19a21f47819 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 24 Aug 2023 03:59:09 -0700 Subject: [PATCH 200/263] Fix newly flashed units having no brightness --- usermods/Tubes/controller.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 514d2a9f0d..3e1481745b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -213,7 +213,7 @@ class PatternController : public MessageReceiver { options.brightness = DEFAULT_TUBE_BRIGHTNESS; } options.debugging = false; - load_options(options); + load_options(options, true); #ifdef USELCD lcd->setup(); @@ -575,9 +575,9 @@ class PatternController : public MessageReceiver { Serial.println(); } - void load_options(ControllerOptions &options) { + void load_options(ControllerOptions &options, bool init=false) { // Power-saving devices retain their WLED brightness - if (!power_save) + if (init || !power_save) strip.setBrightness(options.brightness); } From 09dbcfc30f4545ae94b2fd0a5cea2bcc29b78da0 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Apr 2024 12:12:17 -0700 Subject: [PATCH 201/263] Christmas and Golden modes --- usermods/Tubes/controller.h | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3e1481745b..24cfac996f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -165,6 +165,9 @@ class PatternController : public MessageReceiver { } bool isMasterRole() { +#if defined(GOLDEN) || defined(CHRISTMAS) + return true; +#endif return role >= MasterRole; } @@ -212,6 +215,9 @@ class PatternController : public MessageReceiver { } else { options.brightness = DEFAULT_TUBE_BRIGHTNESS; } +#if defined(GOLDEN) || defined(CHRISTMAS) + node->reset(0xFFF); +#endif options.debugging = false; load_options(options, true); @@ -670,8 +676,22 @@ class PatternController : public MessageReceiver { // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { +#if defined(GOLDEN) + uint r = random8(0, 4); + uint colors[4] = {18, 58, 71, 111}; + next_state.palette_id = colors[r]; +#elif defined(CHRISTMAS) // 81, 107 are too bright + uint r = random8(0, 20); + uint colors[20] = {/*gold:*/18, 58, 71, 111, + /*yes:*/25, 34, 61, 63, 81, 112, + /*best yes:*/25, 34, 34, 61, 63, 81, 112, + /*maybe:*/81, 28, 107}; + next_state.palette_id = colors[r]; +#else // Don't select the built-in palettes next_state.palette_id = random8(6, gGradientPaletteCount); +#endif + auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); // Change color more often in boring patterns @@ -948,7 +968,8 @@ class PatternController : public MessageReceiver { // If not the lead, send it to the lead. uint8_t b; accum88 arg = parse_number(command+1); - + Serial.printf("[command=%c arg=%04x]\n", command[0], arg); + switch (command[0]) { case 'd': setDebugging(!options.debugging); From 4c873071cc73aac3b928d2dc3419af29ee988e5c Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 13 Aug 2023 10:30:33 -0400 Subject: [PATCH 202/263] Documented the branch Remove use of "this" Register Dig Unos (and Dig2Go) Fix mistaken refactor that was crashing fix bug due to refactor Fix more errors introduced by refactor FX blending POC Blending Blend tweaking. FX optimisation Chasing memory corruption/leaks. Segment reset Comments from Aircoookie addressed Clear status pixels Cleaner transition code. Fixed skipped pixel flashing. Try to not do multiple changes at the same time, to prevent a jumpy transition Fix the previous fix Fix issues that could cause crashes if flashed with wrong version Make the powersave option persistent --- platformio.ini | 10 + readme.md | 47 ++ usermods/Tubes/Tubes.h | 20 +- usermods/Tubes/beats.h | 47 +- usermods/Tubes/controller.h | 695 ++++++++++++++++------------- usermods/Tubes/debug.h | 56 +-- usermods/Tubes/default_config.json | 2 +- usermods/Tubes/effects.h | 30 +- usermods/Tubes/firmware.sh | 4 +- usermods/Tubes/global_state.h | 22 +- usermods/Tubes/led_strip.h | 24 +- usermods/Tubes/master.h | 100 ++--- usermods/Tubes/node.h | 149 ++++--- usermods/Tubes/options.h | 22 +- usermods/Tubes/particle.h | 66 +-- usermods/Tubes/sound.h | 16 +- usermods/Tubes/timer.h | 34 +- usermods/Tubes/virtual_strip.h | 89 ++-- wled00/FX.cpp | 181 +++----- wled00/FX.h | 67 +-- wled00/FX_2Dfcn.cpp | 16 +- wled00/FX_fcn.cpp | 238 +++++++--- wled00/bus_manager.cpp | 4 + wled00/json.cpp | 3 + wled00/led.cpp | 8 +- 25 files changed, 1101 insertions(+), 849 deletions(-) diff --git a/platformio.ini b/platformio.ini index 29f1ced751..54a53eb682 100644 --- a/platformio.ini +++ b/platformio.ini @@ -399,6 +399,16 @@ lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +[env:esp32_quinled_diguno] +extends = env:esp32dev +build_flags = ${env:esp32dev.build_flags} -D RLYPIN=12 -D BTNPIN=0 -D DATA_PINS=16 -D DMTYPE=1 -D I2S_SDPIN=19 -D I2S_WSPIN=4 -D I2S_CKPIN=18 + -D USERMOD_AUDIOREACTIVE +lib_deps = ${env:esp32dev.lib_deps} + https://github.com/blazoncek/arduinoFFT.git +upload_speed = 690000 +board_build.f_flash = 80000000L +board_build.flash_mode = qio + [env:esp32dev_qio80] board = esp32dev platform = ${esp32.platform} diff --git a/readme.md b/readme.md index dda6634a1e..2394dd5412 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,8 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! +Now with new magical sync powers! + ## ⚙️ Features - WS2812FX library with more than 100 special effects - FastLED noise effects and 50 palettes @@ -59,6 +61,51 @@ See the [documentation on our official site](https://kno.wled.ge)! See [here](https://kno.wled.ge/basics/compatible-hardware)! +## WLEDtubes branch changes + +This is a WLED-based update to my [2019 light tube project](https://github.com/SteveEisner/tubes), which ran on Teensy + FastLED + nRF24L01 radios. + +Most of the changes are in the usermod `usermods/Tubes`: +* Tubes is installed as an overlay function, which allows it to run WLED "underneath" but completely control the output. +* Its final output is a composition of multiple layers: it starts with WLED's output, then optionally overwrites with its own patterns, then runs a particle effects library on top of that. +* It stores a curated playlist of composite effects. Some are WLED stock FX, others are custom-built. It moves randomly through that playlist. +* At the time of writing, WLED could not correctly fade transitions between 2 FX. Tubes makes up for that by being able to fade between 1 WLED FX and 1 custom overlay pattern, or between 2 custom overlays. As it moves through the playlist, it ensures that it never plays two WLED FX in a row. +* The particle library runs on top of everything (including WLED FX) and introduces a variety of patterns & blit effects. +* If the user changes effects or palettes in the WLED web UI, Tubes will honor that & stop overlaying for a little while. It will eventually time out and revert to full overlay mode. +* Everything runs on a custom clock that is synced to a specific BPM. The BPM can be changed (manually now, automatic eventually) so that the effects perfectly sync to nearby music. WLED FX don't sync yet but could be speed-adjusted. +* An ESP-Now based mesh network is created, so all devices running this usermod stay in sync with each other, without Wi-Fi. + +Mesh networking is based on a unidirectional broadcast protocol: +* Every device (node) is assigned a random 12-bit ID. This ID can change at any time, although in practice it only changes upon reboot. +* A node begins with the assumption that it is the only node, and therefore it is the leader, which means it's the one controlling patterns, palettes, and effects. +* As a node operates, it regularly broadcasts its status via ESP-Now, in case other device nodes are nearby. Nodes also continuously listen for ESP-Now broadcasts with node status. +* Status messages are identified by the node's ID. If a node receives a broadcast from a lower ID, it ignores it. +* When a node receives a broadcast from a higher ID, it assumes that other node must be the leader. It syncs its status to the leader's status & stops broadcasting its own status +* When a node receives a broadcast from the same ID, it assumes there's been an ID assignment collision and randomizes its own ID. (This happens sometimes even in a 12-bit space.) +* Nodes are assumed to be unstable; they can move or be turned off (or crash.) Status packets include both a current status and 30+ seconds of future states. All nodes can continue to run in sync even if they don't hear from the leader during this time. +* If a node hasn't heard from the leader in a long time (20 sec or more), it assumes the leader has permanently left. It reverts to being its own leader again until it hears from a new leader with higher ID. +* To help boost the leader's effective range, a following node will occasionally relay the leader's commands using the leader's ID. This helps sync devices that are out of range of the leader, but within range of a follower. The effective range of a single ESP32 device has been measured at hundreds feet; relays allow for an even larger mesh range. +* There's a protocol for explicit control, with commands that can be sent to specific nodes or all nodes. This allows a single master remote to directly control the entire mesh. +* This has been tested on 75+ devices in proximity, but theoretically can expand until it saturates ESPNow bandwidth (hundreds of devices? thousands? not sure) + +The Tubes usermod uses several sub-libraries and helper functions: +* beats.h: an 8-bit bpm library that helps the Tubes run patterns at a specific bpm +* node.h: the ESP-Now based mesh network +* particle.h: a particle effects overlay library +* firmware.sh: successful firmware+config mass-autoupdater +* master.h: a remote that overrides & controls all ESP-Now nodes (run from a separate device) +* timer.h: a tiny library to help with timed events + +There are several left-over modules that aren't used any more. +* bluetooth.h: a failed initial attempt to sync over BLE (now unused) +* updater.h + update_server.h: a failed attempt to write a peer-to-peer firmware auto-updater (unreliable) +* sound.h: an initial attempt to create some sound-reactive effect overlays + +Also, there a few changes to core library files: +* New brighter, more vivid color palettes (palettes.h + wled00/FX_fcn.cpp) +* New button-press code to allow a single button to handle Wi-Fi protection (it's only turned on by explicit button press) and "Power-save" for battery operation (wled00/button.cpp) +* Fleet provisioning for flashing dozens of WLED controllers (wled00/wled_serial.cpp disabled to allow it) + ## ✌️ Other Licensed under the MIT license diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 773f259e08..6b43794151 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -82,8 +82,8 @@ class TubesUsermod : public Usermod { void handleOverlayDraw() { // Draw effects layers over whatever WLED is doing. - this->controller.handleOverlayDraw(); - this->debug.handleOverlayDraw(); + controller.handleOverlayDraw(); + debug.handleOverlayDraw(); if (master) master->handleOverlayDraw(); @@ -101,22 +101,22 @@ class TubesUsermod : public Usermod { bool handleButton(uint8_t b) { // Special code for handling the "power save" button if (b == 100) { // Press button 0 for WLED_LONG_POWER_SAVE ms - this->controller.togglePowerSave(); + controller.togglePowerSave(); return true; } if (b == 101) { // Short press button 0 (piggybacks with default) - this->controller.cancelOverrides(); + controller.cancelOverrides(); return true; } if (b == 102) { // Double-click button 0 - this->controller.acknowledge(); - if (this->controller.isSelecting()) { - if (this->controller.isSelected()) - this->controller.deselect(); + controller.acknowledge(); + if (controller.isSelecting()) { + if (controller.isSelected()) + controller.deselect(); else - this->controller.select(); + controller.select(); } else { - this->controller.request_new_bpm(); + controller.request_new_bpm(); } return true; } diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h index 67643f6eef..c60e6b1b5e 100644 --- a/usermods/Tubes/beats.h +++ b/usermods/Tubes/beats.h @@ -19,7 +19,7 @@ class BeatController { globalTimer.setup(); // Starts in phrase 1 - this->sync(DEFAULT_BPM << 8, 0); + sync(DEFAULT_BPM << 8, 0); } void update() @@ -27,41 +27,46 @@ class BeatController { globalTimer.update(); // Maintains an accumulator with 14 bits of precision - this->accum += globalTimer.delta_micros << 8; - while (this->accum > this->micros_per_frac) { - this->frac++; - this->accum -= this->micros_per_frac; + accum += globalTimer.delta_micros << 8; + while (accum > micros_per_frac) { + frac++; + accum -= micros_per_frac; } } - void sync(accum88 bpm, BeatFrame_24_8 frac) { - accum88 last_bpm = this->bpm; - this->bpm = bpm; - this->frac = frac; - this->accum = 0; + void sync(accum88 b, BeatFrame_24_8 f) { + if (b < 40<<8) { + // Reject BPMs that are too low. + return; + } + + accum88 last_bpm = bpm; + bpm = b; + frac = f; + accum = 0; - this->micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); + micros_per_frac = (uint32_t)(15360000000.0 / (float)bpm); - if (last_bpm != this->bpm) - this->print_bpm(); + if (last_bpm != bpm) + print_bpm(); } - void set_bpm(accum88 bpm) { - this->sync(bpm, this->frac); + void set_bpm(accum88 b) { + sync(b, frac); } - void adjust_bpm(saccum78 bpm) { - this->sync(this->bpm + bpm, this->frac); + void adjust_bpm(saccum78 b) { + sync(bpm + b, frac); } void start_phrase() { - this->frac &= -0xFFF; - this->accum = 0; + frac &= -0xFFF; + accum = 0; } void print_bpm() { - Serial.print(this->bpm >> 8); - uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(bpm >> 8); + uint8_t frac = scale8(100, bpm & 0xFF); Serial.print(F(".")); if (frac < 10) Serial.print(F("0")); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 294830df2b..3339cbaaea 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -15,6 +15,8 @@ #include "global_state.h" #include "node.h" +#define EEPSIZE 2560 + const static uint8_t DEFAULT_MASTER_BRIGHTNESS = 200; const static uint8_t DEFAULT_TUBE_BRIGHTNESS = 120; const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; @@ -26,7 +28,7 @@ const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; #define MAX_COLOR_CHANGE_PHRASES 10 #define ROLE_EEPROM_LOCATION 2559 -#define BOOT_OPTIONS_EEPROM_LOCATION 2557 +#define BOOT_OPTIONS_EEPROM_LOCATION 2551 #define IDENTIFY_STUCK_PATTERNS #define IDENTIFY_STUCK_PALETTES @@ -76,26 +78,26 @@ class Button { uint8_t pin; bool lastPressed = false; - void setup(uint8_t pin) { - this->pin = pin; + void setup(uint8_t p) { + pin = p; pinMode(pin, INPUT_PULLUP); - this->debounceTimer.start(0); + debounceTimer.start(0); } bool pressed() { - if (digitalRead(this->pin) == HIGH) { - return !this->debounceTimer.ended(); + if (digitalRead(pin) == HIGH) { + return !debounceTimer.ended(); } - this->debounceTimer.start(DEBOUNCE_TIME); + debounceTimer.start(DEBOUNCE_TIME); return true; } bool triggered() { // Triggers BOTH low->high AND high->low - bool p = this->pressed(); - bool lp = this->lastPressed; - this->lastPressed = p; + bool p = pressed(); + bool lp = lastPressed; + lastPressed = p; return p != lp; } }; @@ -144,158 +146,166 @@ class PatternController : public MessageReceiver { // When a pattern is boring, spice it up a bit with more effects bool isBoring = false; - PatternController(uint8_t num_leds, BeatController *beats) { - this->num_leds = num_leds; + PatternController(uint8_t num, BeatController *b) : num_leds(num), beats(b) { #ifdef USELCD - this->lcd = new Lcd(); + lcd = new Lcd(); #endif - this->led_strip = new LEDs(num_leds); - this->beats = beats; - this->effects = new Effects(); - this->node = new LightNode(this); - // this->mesh = new BLEMeshNode(this); + led_strip = new LEDs(num_leds); + effects = new Effects(); + node = new LightNode(this); + // mesh = new BLEMeshNode(this); for (uint8_t i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED - this->vstrips[i] = new VirtualStrip(num_leds * 2 + 1); + vstrips[i] = new VirtualStrip(num_leds * 2 + 1); #else - this->vstrips[i] = new VirtualStrip(num_leds); + vstrips[i] = new VirtualStrip(num_leds); #endif } } bool isMaster() { - return this->role >= MasterRole; + return role >= MasterRole; } void setup() { - this->node->setup(); - EEPROM.begin(2560); - this->role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); - if (this->role == 255) { - this->role = UnknownRole; + node->setup(); + EEPROM.begin(EEPSIZE); + role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); + if (role == 255) { + role = UnknownRole; } - EEPROM.end(); - Serial.printf("Role = %d\n", this->role); + Serial.printf("Role = %d\n", role); auto b = EEPROM.read(BOOT_OPTIONS_EEPROM_LOCATION); Serial.printf("EEPROM read: %d\n", b); + EEPROM.end(); + BootOptions* boot = (BootOptions*)&b; switch (boot->default_power_save) { case BOOT_OPTION_POWER_SAVE_OFF: - this->power_save = 0; + power_save = 0; break; case BOOT_OPTION_POWER_SAVE_ON: - this->power_save = 1; + power_save = 1; break; default: - this->power_save = (this->role < CampRole); + power_save = (role < CampRole); break; } - if (this->role <= CampRole) + if (role <= CampRole) strip.ablMilliampsMax = 700; // Really limit for batteries - else if (this->role <= InstallationRole) + else if (role <= InstallationRole) strip.ablMilliampsMax = 1000; else strip.ablMilliampsMax = 1400; - if (this->role >= MasterRole) { - this->node->reset(3850 + this->role); // MASTER ID - this->options.brightness = DEFAULT_MASTER_BRIGHTNESS; - } else if (this->role >= LegacyRole) { - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; - } else if (this->role == InstallationRole) { - this->options.brightness = DEFAULT_TANK_BRIGHTNESS; + if (role >= MasterRole) { + node->reset(3850 + role); // MASTER ID + options.brightness = DEFAULT_MASTER_BRIGHTNESS; + } else if (role >= LegacyRole) { + options.brightness = DEFAULT_TUBE_BRIGHTNESS; + } else if (role == InstallationRole) { + options.brightness = DEFAULT_TANK_BRIGHTNESS; } else { - this->options.brightness = DEFAULT_TUBE_BRIGHTNESS; + options.brightness = DEFAULT_TUBE_BRIGHTNESS; } - this->options.debugging = false; - this->load_options(this->options); + options.debugging = false; + load_options(options); #ifdef USELCD - this->lcd->setup(); + lcd->setup(); #endif - this->set_next_pattern(0); - this->set_next_palette(0); - this->set_next_effect(0); - this->next_state.pattern_phrase = 0; - this->next_state.palette_phrase = 0; - this->next_state.effect_phrase = 0; - this->set_wled_palette(0); // Default palette - this->set_wled_pattern(0, 128, 128); // Default pattern - - this->sound.setup(); - - this->updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to + set_next_pattern(0); + set_next_palette(0); + set_next_effect(0); + next_state.pattern_phrase = 0; + next_state.palette_phrase = 0; + next_state.effect_phrase = 0; + set_wled_palette(0); // Default palette + set_wled_pattern(0, 128, 128); // Default pattern + + sound.setup(); + + updateTimer.start(STATUS_UPDATE_PERIOD); // Ready to send an update as soon as we're able to Serial.println("Controller: ok"); } void do_pattern_changes() { - uint16_t phrase = this->current_state.beat_frame >> 12; + uint16_t phrase = current_state.beat_frame >> 12; bool changed = false; - if (phrase >= this->next_state.pattern_phrase) { + if (phrase >= next_state.pattern_phrase) { #ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change pattern"); #endif - this->load_pattern(this->next_state); - this->next_state.pattern_phrase = phrase + this->set_next_pattern(phrase); + load_pattern(next_state); + next_state.pattern_phrase = phrase + set_next_pattern(phrase); + + // Don't change pattern and others at the same time + while (next_state.pattern_phrase == next_state.palette_phrase || next_state.pattern_phrase == next_state.effect_phrase) { + next_state.pattern_phrase += random8(1,3); + } changed = true; } - if (phrase >= this->next_state.palette_phrase) { + if (phrase >= next_state.palette_phrase) { #ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change palette"); #endif - this->load_palette(this->next_state); - this->next_state.palette_phrase = phrase + this->set_next_palette(phrase); + load_palette(next_state); + next_state.palette_phrase = phrase + set_next_palette(phrase); + + // Don't change palette and others at the same time + while (next_state.palette_phrase == next_state.pattern_phrase || next_state.palette_phrase == next_state.effect_phrase) { + next_state.palette_phrase += random8(1,3); + } changed = true; } - if (phrase >= this->next_state.effect_phrase) { + if (phrase >= next_state.effect_phrase) { #ifdef IDENTIFY_STUCK_PATTERNS Serial.println("Time to change effect"); #endif - this->load_effect(this->next_state); - this->next_state.effect_phrase = phrase + this->set_next_effect(phrase); + load_effect(next_state); + next_state.effect_phrase = phrase + set_next_effect(phrase); + + // Don't change palette and others at the same time + while (next_state.effect_phrase == next_state.pattern_phrase || next_state.effect_phrase == next_state.palette_phrase) { + next_state.effect_phrase += random8(1,3); + } changed = true; } if (changed) { - // For now, WLED doesn't handle transitioning pattern & palette well. - // Stagger them - if (this->next_state.pattern_phrase == this->next_state.palette_phrase) { - this->next_state.palette_phrase += random8(1,3); - } - - this->next_state.print(); + next_state.print(); Serial.println(); } } void cancelOverrides() { // Release the WLED overrides and take over control of the strip again. - this->paletteOverrideTimer.stop(); - this->patternOverrideTimer.stop(); + paletteOverrideTimer.stop(); + patternOverrideTimer.stop(); } void enterSelectMode() { - this->selectTimer.start(20000); + selectTimer.start(20000); } bool isSelecting() { - return !this->selectTimer.ended(); + return !selectTimer.ended(); } bool isSelected() { - return this->updater.status == Ready; + return updater.status == Ready; } void select(bool selected = true) { if (selected) - this->updater.ready(); + updater.ready(); else { - this->updater.stop(); + updater.stop(); WiFi.softAPdisconnect(true); } } @@ -305,115 +315,115 @@ class PatternController : public MessageReceiver { } void set_palette_override(uint8_t value) { - if (!this->canOverride) + if (!canOverride) return; - if (value == this->paletteOverride) + if (value == paletteOverride) return; - this->paletteOverride = value; + paletteOverride = value; if (value) { Serial.println("WLED has control of palette."); - this->paletteOverrideTimer.start(300000); // 5 minutes of manual control + paletteOverrideTimer.start(300000); // 5 minutes of manual control } else { Serial.println("Turning off WLED control of palette."); - this->paletteOverrideTimer.stop(); - this->set_wled_palette(this->current_state.palette_id); + paletteOverrideTimer.stop(); + set_wled_palette(current_state.palette_id); } } void set_pattern_override(uint8_t value, uint8_t auto_mode) { - if (!this->canOverride) + if (!canOverride) return; - if (value == DEFAULT_WLED_FX && !this->patternOverride) + if (value == DEFAULT_WLED_FX && !patternOverride) return; - if (value == this->patternOverride) + if (value == patternOverride) return; - this->patternOverride = value; + patternOverride = value; if (value) { Serial.println("WLED has control of patterns."); - this->patternOverrideTimer.start(300000); // 5 minutes of manual control + patternOverrideTimer.start(300000); // 5 minutes of manual control transitionDelay = 500; // Short transitions } else { Serial.println("Turning off WLED control of patterns."); - this->patternOverrideTimer.stop(); + patternOverrideTimer.stop(); transitionDelay = 8000; // Back to long transitions uint8_t param = modeParameter(auto_mode); - this->set_wled_pattern(auto_mode, param, param); + set_wled_pattern(auto_mode, param, param); } } void update() { - this->read_keys(); + read_keys(); // Update the mesh - this->node->update(); + node->update(); // Update sound meter - this->sound.update(); + sound.update(); // Update patterns to the beat - this->update_beat(); + update_beat(); Segment& segment = strip.getMainSegment(); // You can only go into manual control after enabling the wifi - if (apActive && this->updater.status != Ready) - this->canOverride = true; + if (apActive && updater.status != Ready) + canOverride = true; // Detect manual overrides & update the current state to match. - if (this->canOverride) { - if (this->paletteOverride && (this->paletteOverrideTimer.ended() || !apActive)) { - this->set_palette_override(0); - } else if (segment.palette != this->current_state.palette_id) { - this->set_palette_override(segment.palette); + if (canOverride) { + if (paletteOverride && (paletteOverrideTimer.ended() || !apActive)) { + set_palette_override(0); + } else if (segment.palette != current_state.palette_id) { + set_palette_override(segment.palette); } - uint8_t wled_mode = gPatterns[this->current_state.pattern_id].wled_fx_id; + uint8_t wled_mode = gPatterns[current_state.pattern_id].wled_fx_id; if (wled_mode < 10) wled_mode = DEFAULT_WLED_FX; - if (this->patternOverride && (this->patternOverrideTimer.ended() || !apActive)) { - this->set_pattern_override(0, wled_mode); + if (patternOverride && (patternOverrideTimer.ended() || !apActive)) { + set_pattern_override(0, wled_mode); } else if (segment.mode != wled_mode) { - this->set_pattern_override(segment.mode, wled_mode); + set_pattern_override(segment.mode, wled_mode); } } do_pattern_changes(); - if (this->graphicsTimer.every(REFRESH_PERIOD)) { - this->updateGraphics(); + if (graphicsTimer.every(REFRESH_PERIOD)) { + updateGraphics(); } // Update current status - if (this->updateTimer.every(STATUS_UPDATE_PERIOD)) { + if (updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!this->node->is_following() || random(0, 4) == 0) { - this->send_update(); + if (!node->is_following() || random(0, 4) == 0) { + send_update(); } } - this->updater.update(); + updater.update(); #ifdef USELCD - if (this->lcd->active) { - this->lcd->size(1); - this->lcd->write(0,56, this->current_state.beat_frame); - this->lcd->write(80,56, this->x_axis); - this->lcd->write(100,56, this->y_axis); - this->lcd->show(); - - this->lcd->update(); + if (lcd->active) { + lcd->size(1); + lcd->write(0,56, current_state.beat_frame); + lcd->write(80,56, x_axis); + lcd->write(100,56, y_axis); + lcd->show(); + + lcd->update(); } #endif } void handleOverlayDraw() { // In manual mode WLED is always active - if (this->patternOverride) { - this->wled_fader = 0xFFFF; + if (patternOverride) { + wled_fader = 0xFFFF; transition_mode_point = 0; } else if (wled_fader == 0xFFFF) { // When fading down... @@ -429,11 +439,11 @@ class PatternController : public MessageReceiver { uint16_t length = strip.getLengthTotal(); // Crossfade between the custom pattern engine and WLED - uint8_t fader = this->wled_fader >> 8; + uint8_t fader = wled_fader >> 8; if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer for (int i = 0; i < length; i++) { - CRGB c = this->led_strip->getPixelColor(i); + CRGB c = led_strip->getPixelColor(i); if (fader > 0) { CRGB color2 = strip.getPixelColor(i); uint8_t r = blend8(c.r, color2.r, fader); @@ -447,7 +457,7 @@ class PatternController : public MessageReceiver { // Power Save mode: reduce number of displayed pixels // Only affects non-powered poles - if (this->power_save && this->role < InstallationRole) { + if (power_save && role < InstallationRole) { // Screen door effect to save power for (int i = 0; i < length; i++) { if (i % 2) { @@ -456,34 +466,34 @@ class PatternController : public MessageReceiver { } } - this->sound.handleOverlayDraw(); + sound.handleOverlayDraw(); // Draw effects layers over whatever WLED is doing. // But not in manual (WLED) mode - if (!this->patternOverride) { - this->effects->draw(&strip); + if (!patternOverride) { + effects->draw(&strip); } // Make the art half-size if it has a small number of pixels - if (this->role >= MasterRole || this->role == SmallArtRole) { + if (role >= MasterRole || role == SmallArtRole) { int p = 0; for (int i = 0; i < length; i++) { CRGB c = strip.getPixelColor(i++); // i advances by 2 CRGB c2 = strip.getPixelColor(i); nblend(c, c2, 128); - if (this->role >= MasterRole) { + if (role >= MasterRole) { nblend(c, CRGB::Black, 128); } strip.setPixelColor(p++, c); } } - if (this->flashColor) { + if (flashColor) { if (flashTimer.ended()) - this->flashColor = 0; + flashColor = 0; else { if (millis() % 4000 < 2000) { - auto chsv = CHSV(this->flashColor, 255, 255); + auto chsv = CHSV(flashColor, 255, 255); for (int i = 0; i < length; i++) { strip.setPixelColor(i, CRGB(chsv)); } @@ -491,77 +501,77 @@ class PatternController : public MessageReceiver { } } - this->updater.handleOverlayDraw(); + updater.handleOverlayDraw(); } void restart_phrase() { - this->beats->start_phrase(); - this->update_beat(); - this->send_update(); + beats->start_phrase(); + update_beat(); + send_update(); } void set_phrase_position(uint8_t pos) { - this->beats->sync(this->beats->bpm, (this->beats->frac & -0xFFF) + (pos<<8)); - this->update_beat(); - this->send_update(); + beats->sync(beats->bpm, (beats->frac & -0xFFF) + (pos<<8)); + update_beat(); + send_update(); } void set_tapped_bpm(accum88 bpm, uint8_t pos=15) { // By default, restarts at 15th beat - because this is the end of a tap - this->beats->sync(bpm, (this->beats->frac & -0xFFF) + (pos<<8)); - this->update_beat(); - this->send_update(); + beats->sync(bpm, (beats->frac & -0xFFF) + (pos<<8)); + update_beat(); + send_update(); } void request_new_bpm(accum88 new_bpm = 0) { // 0 = toggle 120 to 125 if (new_bpm == 0) - new_bpm = this->current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; + new_bpm = current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; - if (this->node->is_following()) { + if (node->is_following()) { // Send a request up to ROOT - this->broadcast_bpm(new_bpm); + broadcast_bpm(new_bpm); } else { - this->set_tapped_bpm(new_bpm, 0); + set_tapped_bpm(new_bpm, 0); } } void update_beat() { - this->current_state.bpm = this->next_state.bpm = this->beats->bpm; - this->current_state.beat_frame = particle_beat_frame = this->beats->frac; // (particle_beat_frame is a hack) - if (this->current_state.bpm>>8 <= 118) // Hip hop / ghettofunk - this->energy = MediumEnergy; - else if (this->current_state.bpm>>8 >= 125) // House & breaks - this->energy = HighEnergy; - else if (this->current_state.bpm>>8 > 120) // Tech house - this->energy = MediumEnergy; + current_state.bpm = next_state.bpm = beats->bpm; + current_state.beat_frame = particle_beat_frame = beats->frac; // (particle_beat_frame is a hack) + if (current_state.bpm>>8 <= 118) // Hip hop / ghettofunk + energy = MediumEnergy; + else if (current_state.bpm>>8 >= 125) // House & breaks + energy = HighEnergy; + else if (current_state.bpm>>8 > 120) // Tech house + energy = MediumEnergy; else - this->energy = Chill; // Deep house + energy = Chill; // Deep house } void send_update() { Serial.print(" "); - this->current_state.print(); + current_state.print(); Serial.print(F(" ")); - uint16_t phrase = this->current_state.beat_frame >> 12; + uint16_t phrase = current_state.beat_frame >> 12; Serial.print(F(" ")); - Serial.print(this->next_state.pattern_phrase - phrase); + Serial.print(next_state.pattern_phrase - phrase); Serial.print(F("P ")); - Serial.print(this->next_state.palette_phrase - phrase); + Serial.print(next_state.palette_phrase - phrase); Serial.print(F("C ")); - Serial.print(this->next_state.effect_phrase - phrase); + Serial.print(next_state.effect_phrase - phrase); Serial.print(F("E: ")); - this->next_state.print(); + next_state.print(); Serial.print(F(" ")); Serial.println(); - this->broadcast_state(); + broadcast_state(); } void background_changed() { - this->update_background(); - this->current_state.print(); + update_background(); + current_state.print(); Serial.println(); } @@ -570,25 +580,25 @@ class PatternController : public MessageReceiver { } void load_pattern(TubeState &tube_state) { - if (this->current_state.pattern_id == tube_state.pattern_id - && this->current_state.pattern_sync_id == tube_state.pattern_sync_id) + if (current_state.pattern_id == tube_state.pattern_id + && current_state.pattern_sync_id == tube_state.pattern_sync_id) return; - this->current_state.pattern_phrase = tube_state.pattern_phrase; - this->current_state.pattern_id = tube_state.pattern_id % gPatternCount; - this->current_state.pattern_sync_id = tube_state.pattern_sync_id; - this->isBoring = gPatterns[this->current_state.pattern_id].control.energy == Boring; + current_state.pattern_phrase = tube_state.pattern_phrase; + current_state.pattern_id = tube_state.pattern_id % gPatternCount; + current_state.pattern_sync_id = tube_state.pattern_sync_id; + isBoring = gPatterns[current_state.pattern_id].control.energy == Boring; Serial.print(F("Change pattern ")); - this->background_changed(); + background_changed(); } bool isShowingWled() { - return this->current_state.pattern_id >= numInternalPatterns; + return current_state.pattern_id >= numInternalPatterns; } uint8_t modeParameter(uint8_t mode) { - switch (this->energy) { + switch (energy) { case Boring: // Spice things up a bit return 128; @@ -626,15 +636,15 @@ class PatternController : public MessageReceiver { for (int i = 0; i < 10; i++) { pattern_id = get_valid_next_pattern(); def = gPatterns[pattern_id]; - if (def.control.energy <= this->energy) + if (def.control.energy <= energy) break; } #ifdef IDENTIFY_STUCK_PATTERNS Serial.printf("Next pattern will be %d\n", pattern_id); #endif - this->next_state.pattern_id = pattern_id; - this->next_state.pattern_sync_id = this->randomSyncMode(); + next_state.pattern_id = pattern_id; + next_state.pattern_sync_id = randomSyncMode(); switch (def.control.duration) { case ExtraShortDuration: return random8(2, 6); @@ -647,45 +657,45 @@ class PatternController : public MessageReceiver { } void load_palette(TubeState &tube_state) { - if (this->current_state.palette_id == tube_state.palette_id) + if (current_state.palette_id == tube_state.palette_id) return; - this->current_state.palette_phrase = tube_state.palette_phrase; - this->current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; - set_wled_palette(this->current_state.palette_id); + current_state.palette_phrase = tube_state.palette_phrase; + current_state.palette_id = tube_state.palette_id % gGradientPaletteCount; + set_wled_palette(current_state.palette_id); } // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { // Don't select the built-in palettes - this->next_state.palette_id = random8(6, gGradientPaletteCount); + next_state.palette_id = random8(6, gGradientPaletteCount); auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); // Change color more often in boring patterns - if (this->isBoring) { + if (isBoring) { phrases /= 2; } return phrases; } void load_effect(TubeState &tube_state) { - if (this->current_state.effect_params.effect == tube_state.effect_params.effect && - this->current_state.effect_params.pen == tube_state.effect_params.pen && - this->current_state.effect_params.chance == tube_state.effect_params.chance) + if (current_state.effect_params.effect == tube_state.effect_params.effect && + current_state.effect_params.pen == tube_state.effect_params.pen && + current_state.effect_params.chance == tube_state.effect_params.chance) return; - this->_load_effect(tube_state.effect_params); + _load_effect(tube_state.effect_params); } void _load_effect(EffectParameters params) { - this->current_state.effect_params = params; + current_state.effect_params = params; Serial.print(F("Change effect ")); - this->current_state.print(); + current_state.print(); Serial.println(); - this->effects->load(this->current_state.effect_params); + effects->load(current_state.effect_params); } // Choose the effect to display at the next effect cycle @@ -695,11 +705,11 @@ class PatternController : public MessageReceiver { // Pick a random effect to add; boring patterns get better chance at having an effect. EffectDef def = gEffects[effect_num]; - if (def.control.energy > this->energy) { + if (def.control.energy > energy) { def = gEffects[0]; } - this->next_state.effect_params = def.params; + next_state.effect_params = def.params; switch (def.control.duration) { case ExtraShortDuration: return random(1,3); @@ -713,20 +723,20 @@ class PatternController : public MessageReceiver { void update_background() { Background background; - background.animate = gPatterns[this->current_state.pattern_id].backgroundFn; - background.wled_fx_id = gPatterns[this->current_state.pattern_id].wled_fx_id; - background.palette_id = this->current_state.palette_id; - background.sync = (SyncMode)this->current_state.pattern_sync_id; + background.animate = gPatterns[current_state.pattern_id].backgroundFn; + background.wled_fx_id = gPatterns[current_state.pattern_id].wled_fx_id; + background.palette_id = current_state.palette_id; + background.sync = (SyncMode)current_state.pattern_sync_id; // Use one of the virtual strips to render the patterns. // A WLED-based pattern exists on the virtual strip, but causes // it to do nothing since WLED merging happens in handleOverlayDraw. // Reuse virtual strips to prevent heap fragmentation for (uint8_t i = 0; i < NUM_VSTRIPS; i++) { - this->vstrips[i]->fadeOut(); + vstrips[i]->fadeOut(); } - this->vstrips[this->next_vstrip]->load(background); - this->next_vstrip = (this->next_vstrip + 1) % NUM_VSTRIPS; + vstrips[next_vstrip]->load(background); + next_vstrip = (next_vstrip + 1) % NUM_VSTRIPS; uint8_t param = modeParameter(background.wled_fx_id); set_wled_pattern(background.wled_fx_id, param, param); @@ -734,12 +744,12 @@ class PatternController : public MessageReceiver { } bool isUnderWledControl() { - return this->paletteOverride || this->patternOverride; + return paletteOverride || patternOverride; } void set_wled_palette(uint8_t palette_id) { - if (this->paletteOverride) - palette_id = this->paletteOverride; + if (paletteOverride) + palette_id = paletteOverride; for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); @@ -753,13 +763,13 @@ class PatternController : public MessageReceiver { } void set_wled_pattern(uint8_t pattern_id, uint8_t speed, uint8_t intensity) { - if (this->patternOverride) - pattern_id = this->patternOverride; + if (patternOverride) + pattern_id = patternOverride; else if (pattern_id == 0) pattern_id = DEFAULT_WLED_FX; // Never set it to solid // When fading IN, make the pattern transition immediate if possible - bool fadeIn = this->wled_fader < 2000; + bool fadeIn = wled_fader < 2000; for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive()) continue; @@ -777,31 +787,27 @@ class PatternController : public MessageReceiver { stateUpdated(CALL_MODE_DIRECT_CHANGE); } - static void set_wled_brightness(uint8_t brightness) { - strip.setBrightness(brightness); - } - void setBrightness(uint8_t brightness) { Serial.printf("brightness: %d\n", brightness); - this->options.brightness = brightness; - this->broadcast_options(); + options.brightness = brightness; + broadcast_options(); } void setDebugging(bool debugging) { Serial.printf("debugging: %d\n", debugging); - this->options.debugging = debugging; - this->broadcast_options(); + options.debugging = debugging; + broadcast_options(); } void togglePowerSave() { - setPowerSave(!this->power_save); + setPowerSave(!power_save); } - void setPowerSave(bool power_save) { + void setPowerSave(bool ps) { + power_save = ps; Serial.printf("power_save: %d\n", power_save); - this->power_save = power_save; // Remember this setting on the next boot EEPROM.begin(2560); @@ -816,10 +822,10 @@ class PatternController : public MessageReceiver { EEPROM.end(); } - void setRole(ControllerRole role) { - this->role = role; + void setRole(ControllerRole r) { + role = r; Serial.printf("Role = %d", role); - EEPROM.begin(2560); + EEPROM.begin(EEPSIZE); EEPROM.write(ROLE_EEPROM_LOCATION, role); EEPROM.write(BOOT_OPTIONS_EEPROM_LOCATION, 0); // Reset all boot options EEPROM.end(); @@ -831,7 +837,7 @@ class PatternController : public MessageReceiver { uint8_t r = random8(128); // For boring patterns, up the chance of a sync mode - if (this->isBoring) + if (isBoring) r -= 20; if (r < 30) @@ -847,7 +853,7 @@ class PatternController : public MessageReceiver { void updateGraphics() { static BeatFrame_24_8 lastFrame = 0; - BeatFrame_24_8 beat_frame = this->current_state.beat_frame; + BeatFrame_24_8 beat_frame = current_state.beat_frame; uint8_t beat_pulse = 0; for (int i = 0; i < 8; i++) { @@ -856,11 +862,11 @@ class PatternController : public MessageReceiver { } lastFrame = beat_frame; - this->wled_fader = 0; + wled_fader = 0; VirtualStrip *first_strip = NULL; for (uint8_t i=0; i < NUM_VSTRIPS; i++) { - VirtualStrip *vstrip = this->vstrips[i]; + VirtualStrip *vstrip = vstrips[i]; if (vstrip->fade == Dead) continue; @@ -870,13 +876,13 @@ class PatternController : public MessageReceiver { // Remember the strip that's actually WLED if (vstrip->isWled()) - this->wled_fader = vstrip->fader; + wled_fader = vstrip->fader; vstrip->update(beat_frame, beat_pulse); - vstrip->blend(this->led_strip->leds, this->led_strip->num_leds, this->options.brightness, vstrip == first_strip); + vstrip->blend(led_strip->leds, led_strip->num_leds, options.brightness, vstrip == first_strip); } - this->effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); + effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); } virtual void acknowledge() { @@ -888,14 +894,14 @@ class PatternController : public MessageReceiver { return; char c = Serial.read(); - char *k = this->key_buffer; - uint8_t max = sizeof(this->key_buffer); + char *k = key_buffer; + uint8_t max = sizeof(key_buffer); for (uint8_t i=0; *k && (i < max-1); i++) { k++; } if (c == 10) { - this->keyboard_command(this->key_buffer); - this->key_buffer[0] = 0; + keyboard_command(key_buffer); + key_buffer[0] = 0; } else { *k++ = c; *k = 0; @@ -931,37 +937,37 @@ class PatternController : public MessageReceiver { void keyboard_command(char *command) { // If not the lead, send it to the lead. uint8_t b; - accum88 arg = this->parse_number(command+1); + accum88 arg = parse_number(command+1); switch (command[0]) { case 'd': - this->setDebugging(!this->options.debugging); + setDebugging(!options.debugging); break; case '~': doReboot = true; break; case '@': - this->togglePowerSave(); + togglePowerSave(); break; case '-': - b = this->options.brightness; + b = options.brightness; while (*command++ == '-') b -= 5; - this->setBrightness(b - 5); + setBrightness(b - 5); break; case '+': - b = this->options.brightness; + b = options.brightness; while (*command++ == '+') b += 5; - this->setBrightness(b + 5); + setBrightness(b + 5); return; case 'l': if (arg < 5*256) { Serial.println(F("nope")); return; } - this->setBrightness(arg >> 8); + setBrightness(arg >> 8); return; case 'b': @@ -973,51 +979,51 @@ class PatternController : public MessageReceiver { return; case 's': - this->beats->start_phrase(); - this->update_beat(); - this->send_update(); + beats->start_phrase(); + update_beat(); + send_update(); return; case 'n': - this->force_next(); + force_next(); return; case 'p': - this->next_state.pattern_phrase = 0; - this->next_state.pattern_id = arg >> 8; - this->next_state.pattern_sync_id = All; - this->broadcast_state(); + next_state.pattern_phrase = 0; + next_state.pattern_id = arg >> 8; + next_state.pattern_sync_id = All; + broadcast_state(); return; case 'm': - this->next_state.pattern_phrase = 0; - this->next_state.pattern_id = this->current_state.pattern_id; - this->next_state.pattern_sync_id = arg >> 8; - this->broadcast_state(); + next_state.pattern_phrase = 0; + next_state.pattern_id = current_state.pattern_id; + next_state.pattern_sync_id = arg >> 8; + broadcast_state(); return; case 'c': - this->next_state.palette_phrase = 0; - this->next_state.palette_id = arg >> 8; - this->broadcast_state(); + next_state.palette_phrase = 0; + next_state.palette_id = arg >> 8; + broadcast_state(); return; case 'e': - this->next_state.effect_phrase = 0; - this->next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; - this->broadcast_state(); + next_state.effect_phrase = 0; + next_state.effect_params = gEffects[(arg >> 8) % gEffectCount].params; + broadcast_state(); return; case '%': - this->next_state.effect_phrase = 0; - this->next_state.effect_params = this->current_state.effect_params; - this->next_state.effect_params.chance = arg; - this->broadcast_state(); + next_state.effect_phrase = 0; + next_state.effect_params = current_state.effect_params; + next_state.effect_params.chance = arg; + broadcast_state(); return; case 'i': Serial.printf("Reset! ID -> %03X\n", arg >> 4); - this->node->reset(arg >> 4); + node->reset(arg >> 4); return; case 'U': @@ -1036,7 +1042,7 @@ class PatternController : public MessageReceiver { .key = command[0], .arg = (uint8_t)(arg >> 8) }; - this->broadcast_action(action); + broadcast_action(action); return; } @@ -1044,9 +1050,9 @@ class PatternController : public MessageReceiver { // Toggle power save Action action = { .key = command[0], - .arg = !this->power_save, + .arg = !power_save, }; - this->broadcast_action(action); + broadcast_action(action); break; } @@ -1054,14 +1060,14 @@ class PatternController : public MessageReceiver { // Toggle sound overlay Action action = { .key = command[0], - .arg = !this->sound.overlay + .arg = !sound.overlay }; - this->broadcast_action(action); + broadcast_action(action); break; } case 'r': - this->setRole((ControllerRole)(arg >> 8)); + setRole((ControllerRole)(arg >> 8)); return; case '?': @@ -1090,7 +1096,7 @@ class PatternController : public MessageReceiver { return; case 'u': - this->updater.start(); + updater.start(); return; case 0: @@ -1104,40 +1110,43 @@ class PatternController : public MessageReceiver { } void force_next() { - uint16_t phrase = this->current_state.beat_frame >> 12; - uint16_t next_phrase = min(this->next_state.pattern_phrase, min(this->next_state.palette_phrase, this->next_state.effect_phrase)) - phrase; - this->next_state.pattern_phrase -= next_phrase; - this->next_state.palette_phrase -= next_phrase; - this->next_state.effect_phrase -= next_phrase; - this->broadcast_state(); + uint16_t phrase = current_state.beat_frame >> 12; + uint16_t next_phrase = min(next_state.pattern_phrase, min(next_state.palette_phrase, next_state.effect_phrase)) - phrase; + next_state.pattern_phrase -= next_phrase; + next_state.palette_phrase -= next_phrase; + next_state.effect_phrase -= next_phrase; + broadcast_state(); } void broadcast_action(Action& action) { - if (!this->node->is_following()) { - this->onAction(&action); + if (!node->is_following()) { + onAction(&action); } - this->node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); + node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); } void broadcast_info(NodeInfo *info) { - this->node->sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); + node->sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); } void broadcast_state() { - this->node->sendCommand(COMMAND_STATE, &this->current_state, sizeof(TubeStates)); + node->sendCommand(COMMAND_STATE, ¤t_state, sizeof(TubeStates)); } void broadcast_options() { - this->node->sendCommand(COMMAND_OPTIONS, &this->options, sizeof(this->options)); + if (!node->is_following()) { + load_options(options); + } + node->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } void broadcast_autoupdate() { - this->node->sendCommand(COMMAND_UPGRADE, &this->updater.current_version, sizeof(this->updater.current_version)); + node->sendCommand(COMMAND_UPGRADE, &updater.current_version, sizeof(updater.current_version)); } void broadcast_bpm(accum88 bpm) { // Hacked in feature: request a new BPM - this->node->sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); + node->sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); } virtual bool onCommand(CommandId command, void *data) { @@ -1149,11 +1158,11 @@ class PatternController : public MessageReceiver { return true; case COMMAND_OPTIONS: - memcpy(&this->options, data, sizeof(this->options)); - this->load_options(this->options); + memcpy(&options, data, sizeof(options)); + load_options(options); Serial.printf("[debug=%d bri=%d]", - this->options.debugging, - this->options.brightness + options.debugging, + options.brightness ); return true; @@ -1162,32 +1171,32 @@ class PatternController : public MessageReceiver { TubeState state; memcpy(&state, &update_data->current, sizeof(TubeState)); - memcpy(&this->next_state, &update_data->next, sizeof(TubeState)); + memcpy(&next_state, &update_data->next, sizeof(TubeState)); state.print(); - this->next_state.print(); + next_state.print(); // Catch up to this state - this->load_pattern(state); - this->load_palette(state); - this->load_effect(state); - this->beats->sync(state.bpm, state.beat_frame); + load_pattern(state); + load_palette(state); + load_effect(state); + beats->sync(state.bpm, state.beat_frame); return true; } case COMMAND_UPGRADE: - this->updater.start((AutoUpdateOffer*)data); + updater.start((AutoUpdateOffer*)data); return true; case COMMAND_ACTION: - this->onAction((Action*)data); + onAction((Action*)data); return true; case COMMAND_BEATS: // the master control ignores this request, it has its own // beat measuring. - if (this->isMaster()) + if (isMaster()) return false; - this->set_tapped_bpm(*(accum88*)data, 0); + set_tapped_bpm(*(accum88*)data, 0); return true; } @@ -1203,24 +1212,24 @@ class PatternController : public MessageReceiver { return; case 'O': - this->sound.overlay = (action->arg != 0); + sound.overlay = (action->arg != 0); return; case 'X': - if (!this->isSelected()) + if (!isSelected()) return; doReboot = true; return; case 'R': - if (!this->isSelected()) + if (!isSelected()) return; setRole((ControllerRole)(action->arg)); return; case '@': Serial.print("Setting power save to %d\n"); - this->setPowerSave(action->arg); + setPowerSave(action->arg); return; case 'W': @@ -1238,41 +1247,103 @@ class PatternController : public MessageReceiver { case 'F': Serial.println("flash!"); - this->flashTimer.start(20000); - this->flashColor = action->arg; + flashTimer.start(20000); + flashColor = action->arg; return; case 'M': Serial.println("cancel manual mode"); - this->cancelOverrides(); + cancelOverrides(); break; case '*': case '(': Serial.println("enter select mode"); - this->enterSelectMode(); + enterSelectMode(); break; case ')': Serial.println("exit select mode"); - this->deselect(); + deselect(); break; case 'V': // Version check: prepare for update - if (this->updater.current_version.version >= action->arg) + if (updater.current_version.version >= action->arg) break; - this->select(); + select(); break; case 'U': - if (!this->isSelected()) + if (!isSelected()) return; - this->updater.start(); + updater.start(); break; } } -}; +#define WIZMOTE_BUTTON_ON 1 +#define WIZMOTE_BUTTON_OFF 2 +#define WIZMOTE_BUTTON_NIGHT 3 +#define WIZMOTE_BUTTON_ONE 16 +#define WIZMOTE_BUTTON_TWO 17 +#define WIZMOTE_BUTTON_THREE 18 +#define WIZMOTE_BUTTON_FOUR 19 +#define WIZMOTE_BUTTON_BRIGHT_UP 9 +#define WIZMOTE_BUTTON_BRIGHT_DOWN 8 + + virtual bool onButton(uint8_t button_id) { + Serial.printf("Button %d\n", button_id); + switch (button_id) { + case WIZMOTE_BUTTON_ON: + WLED::instance().initAP(true); + acknowledge(); + return true; + + case WIZMOTE_BUTTON_OFF: + WiFi.softAPdisconnect(true); + acknowledge(); + return true; + + case WIZMOTE_BUTTON_ONE: + // Make it interesting - switch to a good pattern and timesync mode + acknowledge(); + return true; + + case WIZMOTE_BUTTON_FOUR: + // Turn on or off debugging + setDebugging(!options.debugging); + acknowledge(); + return true; + + case WIZMOTE_BUTTON_BRIGHT_UP: + if (options.brightness <= 230) + setBrightness(options.brightness + 25); + return true; + + case WIZMOTE_BUTTON_BRIGHT_DOWN: + if (options.brightness >= 25) + setBrightness(options.brightness - 25); + return true; + + case WIZMOTE_BUTTON_NIGHT: + // Turn off debugging and wifi + WiFi.softAPdisconnect(true); + setDebugging(false); + acknowledge(); + return true; + + + // case WIZMOTE_BUTTON_ON : setOn(); stateUpdated(CALL_MODE_BUTTON); break; + // case WIZMOTE_BUTTON_OFF : setOff(); stateUpdated(CALL_MODE_BUTTON); break; + // case WIZMOTE_BUTTON_TWO : presetWithFallback(2, FX_MODE_BREATH, 0); resetNightMode(); break; + // case WIZMOTE_BUTTON_THREE : presetWithFallback(3, FX_MODE_FIRE_FLICKER, 0); resetNightMode(); break; + default: + return false; + } + } + + +}; \ No newline at end of file diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 571b07b0e3..c6870f444c 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -30,16 +30,16 @@ class DebugController { uint32_t lastPhraseTime; uint32_t lastFrame; - DebugController(PatternController *controller) { - this->controller = controller; - this->led_strip = controller->led_strip; - this->node = controller->node; + DebugController(PatternController *c) : controller(c) + { + led_strip = controller->led_strip; + node = controller->node; } void setup() { - this->lastPhraseTime = globalTimer.now_micros; - this->lastFrame = (uint32_t)-1; + lastPhraseTime = globalTimer.now_micros; + lastFrame = (uint32_t)-1; } std::string status_code(NodeStatus status) { @@ -62,8 +62,8 @@ class DebugController { auto knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); auto knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); Serial.printf("\n=== %s%s WiFi[ch%d] %s IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n", - this->controller->node->node_name, - status_code(this->controller->node->status).c_str(), + controller->node->node_name, + status_code(controller->node->status).c_str(), WiFi.channel(), knownSsid.c_str(), knownIp[0], @@ -77,15 +77,15 @@ class DebugController { ); Serial.printf("=== Controller: "); - if (this->controller->isMaster()) { + if (controller->isMaster()) { Serial.print("PRIMARY "); } - if (this->controller->sound.active) { + if (controller->sound.active) { Serial.print("SOUND "); } Serial.printf("role=%d power_save=%d\n", - this->controller->role, - this->controller->power_save + controller->role, + controller->power_save ); // Dump WLED status @@ -102,24 +102,24 @@ class DebugController { seg.speed, seg.intensity ); - if (this->controller->patternOverride) { - Serial.printf(" (PATTERN %d)", this->controller->patternOverride); + if (controller->patternOverride) { + Serial.printf(" (PATTERN %d)", controller->patternOverride); } else { - Serial.printf(" at %d", this->controller->wled_fader); + Serial.printf(" at %d", controller->wled_fader); } - if (this->controller->paletteOverride) { - Serial.printf(" (PALETTE %d)", this->controller->paletteOverride); + if (controller->paletteOverride) { + Serial.printf(" (PALETTE %d)", controller->paletteOverride); } Serial.println(); Serial.printf("=== firmware: v%d from SSID %s %u.%u.%u.%u OTA=%d\n\n", - this->controller->updater.current_version.version, - this->controller->updater.current_version.ssid, - this->controller->updater.current_version.host[0], - this->controller->updater.current_version.ssid[1], - this->controller->updater.current_version.ssid[2], - this->controller->updater.current_version.ssid[3], - this->controller->updater.status + controller->updater.current_version.version, + controller->updater.current_version.ssid, + controller->updater.current_version.host[0], + controller->updater.current_version.ssid[1], + controller->updater.current_version.ssid[2], + controller->updater.current_version.ssid[3], + controller->updater.status ); } @@ -128,16 +128,16 @@ class DebugController { void handleOverlayDraw() { // Show the beat on the master OR if debugging - if (this->controller->options.debugging) { + if (controller->options.debugging) { uint16_t num_leds = strip.getLengthTotal(); - uint8_t p1 = (this->controller->current_state.beat_frame >> 8) % 16; + uint8_t p1 = (controller->current_state.beat_frame >> 8) % 16; strip.setPixelColor(p1, CRGB::White); - uint8_t p2 = scale8(this->controller->node->header.id>>4, num_leds-1); + uint8_t p2 = scale8(controller->node->header.id>>4, num_leds-1); strip.setPixelColor(p2, CRGB::Yellow); - uint8_t p3 = scale8(this->controller->node->header.uplinkId>>4, num_leds-1); + uint8_t p3 = scale8(controller->node->header.uplinkId>>4, num_leds-1); if (p3 == p2) { strip.setPixelColor(p3, CRGB::Green); } else { diff --git a/usermods/Tubes/default_config.json b/usermods/Tubes/default_config.json index 5676f56cf3..d32eb86079 100644 --- a/usermods/Tubes/default_config.json +++ b/usermods/Tubes/default_config.json @@ -1 +1 @@ -{"rev":[1,0],"vid":2307130,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"Tube","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":1000,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0,"freq":0}]},"com":[],"btn":{"max":4,"pull":true,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8,"val":2.8},"tr":{"mode":true,"dur":80,"pal":1,"rpc":5},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"grp":1,"ret":0}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"e131prio":0,"addr":1,"dss":0,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0],"p":0},"hue":{"en":false,"id":1,"iv":25,"recv":{"on":true,"bri":true,"col":true},"ip":[0,0,0,0]},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"remote":{"remote_enabled":false,"linked_remote":""},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":false,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1]},"config":{"squelch":10,"gain":60,"AGC":1},"dynamics":{"limiter":true,"rise":80,"fall":1400},"frequency":{"scale":3},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file +{"rev":[1,0],"vid":2308110,"id":{"mdns":"wled-bfdc58","name":"Light Tube","inv":"Light"},"nw":{"ins":[{"ssid":"","pskl":0,"ip":[0,0,0,0],"gw":[0,0,0,0],"sn":[255,255,255,0]}]},"ap":{"ssid":"WLED-AP","pskl":8,"chan":1,"hide":0,"behav":3,"ip":[4,3,2,1]},"wifi":{"sleep":false},"hw":{"led":{"total":112,"maxpwr":700,"ledma":55,"cct":true,"cr":false,"cb":0,"fps":60,"rgbwm":3,"ld":false,"ins":[{"start":0,"len":112,"pin":[16],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0,"freq":0}]},"com":[],"btn":{"max":4,"pull":true,"ins":[{"type":2,"pin":[0],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]},{"type":0,"pin":[-1],"macros":[0,0,0]}],"tt":32,"mqtt":false},"ir":{"pin":5,"type":0,"sel":true},"relay":{"pin":12,"rev":false},"baud":1152,"if":{"i2c-pin":[-1,-1],"spi-pin":[-1,-1,-1]}},"light":{"scale-bri":100,"pal-mode":0,"aseg":false,"gc":{"bri":1,"col":2.8,"val":2.8},"tr":{"mode":true,"dur":80,"pal":1,"rpc":5},"nl":{"mode":1,"dur":60,"tbri":0,"macro":0}},"def":{"ps":0,"on":true,"bri":128},"if":{"sync":{"port0":21324,"port1":65506,"recv":{"bri":true,"col":true,"fx":true,"grp":1,"seg":false,"sb":false},"send":{"dir":false,"btn":false,"va":false,"hue":false,"macro":false,"grp":1,"ret":0}},"nodes":{"list":false,"bcast":false},"live":{"en":true,"mso":false,"port":5568,"mc":false,"dmx":{"uni":1,"seqskip":false,"e131prio":0,"addr":1,"dss":0,"mode":4},"timeout":25,"maxbri":false,"no-gc":true,"offset":0},"va":{"alexa":false,"macros":[0,0],"p":0},"ntp":{"en":false,"host":"0.wled.pool.ntp.org","tz":0,"offset":0,"ampm":false,"ln":0,"lt":0}},"remote":{"remote_enabled":false,"linked_remote":""},"ol":{"clock":0,"cntdwn":false,"min":0,"max":43,"o12pix":0,"o5m":false,"osec":false},"timers":{"cntdwn":{"goal":[20,1,1,0,0,0],"macro":0},"ins":[]},"ota":{"lock":false,"lock-wifi":false,"pskl":7,"aota":true},"um":{"AudioReactive":{"enabled":true,"analogmic":{"pin":36},"digitalmic":{"type":1,"pin":[19,4,18,-1]},"config":{"squelch":10,"gain":60,"AGC":1},"dynamics":{"limiter":true,"rise":80,"fall":1400},"frequency":{"scale":3},"sync":{"port":11988,"mode":0}}}} \ No newline at end of file diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index 22881c771b..baceddafec 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -54,49 +54,49 @@ class Effects { uint8_t chance; void load(EffectParameters ¶ms) { - this->effect = params.effect; - this->pen = params.pen; - this->beat = params.beat; - this->chance = params.chance; + effect = params.effect; + pen = params.pen; + beat = params.beat; + chance = params.chance; } void update(VirtualStrip *strip, BeatFrame_24_8 beat_frame, BeatPulse beat_pulse) { - if (!this->beat || beat_pulse & this->beat) { + if (!beat || beat_pulse & beat) { - if (random8() <= this->chance) { + if (random8() <= chance) { CRGB color = strip->palette_color(random8()); - switch (this->effect) { + switch (effect) { case None: break; case Glitter: - addGlitter(color, this->pen); + addGlitter(color, pen); break; case Beatbox1: case Beatbox2: - addBeatbox(color, this->pen); - if (this->effect == Beatbox2) - addBeatbox(color, this->pen); + addBeatbox(color, pen); + if (effect == Beatbox2) + addBeatbox(color, pen); break; case Bubble: - addBubble(color, this->pen); + addBubble(color, pen); break; case Spark: - addSpark(color, this->pen); + addSpark(color, pen); break; case Flash: - addFlash(CRGB::White, this->pen); + addFlash(CRGB::White, pen); break; } } } - this->animate(beat_frame, beat_pulse); + animate(beat_frame, beat_pulse); } void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index b8b5d81698..e92d0b4a13 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -10,7 +10,7 @@ upload_firmware() { echo "Uploading firmware" sftp control@brcac.com </dev/null + curl -s -F "update=@../../build_output/firmware/tubes.bin" $1/update >/dev/null echo "Updated; wait..." sleep 5 update_config $1 diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index ba1b61d4bd..1677b74de9 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -22,28 +22,28 @@ class TubeState { EffectParameters effect_params; void print() { - uint16_t phrase = this->beat_frame >> 12; + uint16_t phrase = beat_frame >> 12; Serial.print(F("[")); Serial.print(phrase); Serial.print(F(".")); - Serial.print((this->beat_frame >> 8) % 16); + Serial.print((beat_frame >> 8) % 16); Serial.print(F(" P")); - Serial.print(this->pattern_id); + Serial.print(pattern_id); Serial.print(F(",")); - Serial.print(this->pattern_sync_id); + Serial.print(pattern_sync_id); Serial.print(F(" C")); - Serial.print(this->palette_id); + Serial.print(palette_id); Serial.print(F(" E")); - Serial.print(this->effect_params.effect); + Serial.print(effect_params.effect); Serial.print(F(",")); - Serial.print(this->effect_params.pen); + Serial.print(effect_params.pen); Serial.print(F(",")); - Serial.print(this->effect_params.beat); + Serial.print(effect_params.beat); Serial.print(F(",")); - Serial.print(this->effect_params.chance); + Serial.print(effect_params.chance); Serial.print(F(" ")); - Serial.print(this->bpm >> 8); - uint8_t frac = scale8(100, this->bpm & 0xFF); + Serial.print(bpm >> 8); + uint8_t frac = scale8(100, bpm & 0xFF); Serial.print(F(".")); if (frac < 10) Serial.print(F("0")); diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 96830f87e8..7edf523956 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -17,9 +17,7 @@ class LEDs { uint16_t fps = 0; - LEDs(int num_leds=MAX_REAL_LEDS) { - this->num_leds = num_leds; - } + LEDs(int num=MAX_REAL_LEDS) : num_leds(num) { }; void setup() { Serial.println((char *)F("LEDs: ok")); @@ -27,28 +25,28 @@ class LEDs { void reverse() { for (int i=1; i<8; i++) { - CRGB c = this->leds[i]; - this->leds[i] = this->leds[16-i]; - this->leds[16-i] = c; + CRGB c = leds[i]; + leds[i] = leds[16-i]; + leds[16-i] = c; } } void update() { - EVERY_N_MILLISECONDS( this->TARGET_REFRESH ) { - this->fps++; + EVERY_N_MILLISECONDS( TARGET_REFRESH ) { + fps++; } EVERY_N_MILLISECONDS( 1000 ) { - if (this->fps < (TARGET_FRAMES_PER_SECOND - 30)) { - Serial.print(this->fps); + if (fps < (TARGET_FRAMES_PER_SECOND - 30)) { + Serial.print(fps); Serial.println((char *)F(" fps!")); } - this->fps = 0; + fps = 0; } } CRGB getPixelColor(uint8_t pos) { - if (pos > this->num_leds) + if (pos > num_leds) return CRGB::Black; - return this->leds[pos]; + return leds[pos]; } }; diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index b341b73481..3ae0b2303a 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -28,16 +28,14 @@ class Master { PatternController *controller; Button button[5]; - Master(PatternController *controller) { - this->controller = controller; - } + Master(PatternController *c) : controller(c) { }; void setup() { - this->button[0].setup(BUTTON_PIN_1); - this->button[1].setup(BUTTON_PIN_2); - this->button[2].setup(BUTTON_PIN_3); - this->button[3].setup(BUTTON_PIN_4); - this->button[4].setup(BUTTON_PIN_5); + button[0].setup(BUTTON_PIN_1); + button[1].setup(BUTTON_PIN_2); + button[2].setup(BUTTON_PIN_3); + button[3].setup(BUTTON_PIN_4); + button[4].setup(BUTTON_PIN_5); Serial.println((char *)F("Master: ok")); } @@ -45,24 +43,24 @@ class Master { for (uint8_t i=0; i < 5; i++) { if (button[i].triggered()) { if (button[i].pressed()) - this->onButtonPress(i); + onButtonPress(i); else - this->onButtonRelease(i); + onButtonRelease(i); } } - if (this->taps && this->perTapTimer.ended()) { - if (this->taps == 2) { - this->ok(); + if (taps && perTapTimer.ended()) { + if (taps == 2) { + ok(); } else { - this->fail(); + fail(); } - this->taps = 0; + taps = 0; } } void handleOverlayDraw() { - this->updateStatus(this->controller); + updateStatus(controller); } void ok() { @@ -73,68 +71,68 @@ class Master { addFlash(CRGB::Red); } - void onButtonPress(uint8_t button) { - if (button == 0) + void onButtonPress(uint8_t b) { + if (b == 0) return; - if (button == 4) { + if (b == 4) { Serial.println((char *)F("Skip >>")); - this->controller->force_next(); - this->ok(); + controller->force_next(); + ok(); return; } - if (button == 3) { - this->tap(); + if (b == 3) { + tap(); return; } Serial.print((char *)F("Pressed ")); - Serial.println(button); + Serial.println(b); } - void onButtonRelease(uint8_t button) { + void onButtonRelease(uint8_t b) { #ifdef EXTRA_STUFF - if (button == 2) { - if (this->palette_mode) - this->controller->_load_palette(this->palette_id); - this->palette_mode = false; + if (b == 2) { + if (palette_mode) + controller->_load_palette(palette_id); + palette_mode = false; } #endif - if (button == 3) { - if (this->taps == 0) + if (b == 3) { + if (taps == 0) return; - this->tap(); + tap(); return; } Serial.print((char *)F("Released ")); - Serial.println(button); + Serial.println(b); } void tap() { - if (!this->taps) { - this->tapTimer.start(0); + if (!taps) { + tapTimer.start(0); } - this->perTapTimer.start(1500); + perTapTimer.start(1500); - uint32_t time = this->tapTimer.since_mark(); - this->tapTime[this->taps++] = time; + uint32_t time = tapTimer.since_mark(); + tapTime[taps++] = time; uint32_t bpm = 0; - if (this->taps > 4) { + if (taps > 4) { // Can study this later to make BPM detection better // Should be 60000; fudge a bit to adjust to real-world timings - bpm = 60220*256*(this->taps-1) / time; // 120 beats per min = 500ms per beat + bpm = 60220*256*(taps-1) / time; // 120 beats per min = 500ms per beat if (bpm < 70*256) bpm *= 2; else if (bpm > 140*256) bpm /= 2; } - Serial.printf("tap %d: ", this->taps); + Serial.printf("tap %d: ", taps); Serial.print(bpm >> 8); uint8_t f = scale8(100, bpm & 0xFF); Serial.print("."); @@ -142,9 +140,9 @@ class Master { Serial.print("0"); Serial.println(f); - if (this->taps == 16) { + if (taps == 16) { Serial.println("OK! taps"); - this->taps = 0; + taps = 0; auto frac = bpm % 256; // Slight snap to beat @@ -153,18 +151,18 @@ class Master { else if (frac > 128) bpm += (256-frac) / 2; - this->controller->set_tapped_bpm(bpm); - this->ok(); - } else if (this->taps >= 2) { - this->controller->set_tapped_bpm(this->controller->current_state.bpm, taps-1); + controller->set_tapped_bpm(bpm); + ok(); + } else if (taps >= 2) { + controller->set_tapped_bpm(controller->current_state.bpm, taps-1); } } void updateStatus(PatternController *controller) { - if (this->taps) { - this->displayProgress(this->taps); - } else if (this->palette_mode) { - this->displayPalette(this->background); + if (taps) { + displayProgress(taps); + } else if (palette_mode) { + displayPalette(background); } else { uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; strip.setPixelColor(15 - beat_pos, CRGB::White); diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f40b2e8230..fcb3717be7 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -79,6 +79,10 @@ class MessageReceiver { // Abstract: subclasses must define return false; } + virtual bool onButton(uint8_t button_id) { + // Abstract: subclasses must define + return false; + } }; typedef enum{ @@ -102,41 +106,39 @@ class LightNode { Timer uplinkTimer; // When this timer ends, assume uplink is lost. Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink - LightNode(MessageReceiver *receiver) { + LightNode(MessageReceiver *r) : receiver(r) { LightNode::instance = this; - - this->receiver = receiver; } void onWifiConnect() { - if (this->status == NODE_STATUS_QUIET) + if (status == NODE_STATUS_QUIET) return; Serial.println("WiFi connected: stop broadcasting"); quickEspNow.stop(); - this->status = NODE_STATUS_QUIET; - this->rebroadcastTimer.stop(); - this->statusTimer.start(WIFI_CHECK_RATE); + status = NODE_STATUS_QUIET; + rebroadcastTimer.stop(); + statusTimer.start(WIFI_CHECK_RATE); } void onWifiDisconnect() { - if (this->status != NODE_STATUS_QUIET) + if (status != NODE_STATUS_QUIET) return; Serial.println("WiFi disconnected: start broadcasting"); WiFi.mode (WIFI_MODE_STA); WiFi.disconnect(false, true); quickEspNow.begin(1, WIFI_IF_STA); - this->start(); + start(); } void onMeshChange() { - sprintf(this->node_name, + sprintf(node_name, "Tube %03X:%03X", - this->header.id, - this->header.uplinkId + header.id, + header.uplinkId ); - this->configure_ap(); + configure_ap(); } void configure_ap() { @@ -156,40 +158,40 @@ class LightNode { void start() { // Initialization timer: wait for a bit before trying to broadcast. // If this node's ID is high, it's more likely to be the leader, so wait less. - this->status = NODE_STATUS_STARTING; - this->statusTimer.start(3000 - this->header.id / 2); - this->rebroadcastTimer.stop(); + status = NODE_STATUS_STARTING; + statusTimer.start(3000 - header.id / 2); + rebroadcastTimer.stop(); } void onPeerPing(MeshNodeHeader* node) { // When receiving a message, if the IDs match, it's a conflict // Reset to create a new ID. - if (node->id == this->header.id) { + if (node->id == header.id) { Serial.println("Detected an ID conflict."); - this->reset(); + reset(); } // If the message arrives from a higher ID, switch into follower mode - if (node->id > this->header.uplinkId && node->id > this->header.id) { + if (node->id > header.uplinkId && node->id > header.id) { #ifdef RELAY_DEBUGGING // When debugging relay, pretend not to see any nodes above 0x800 if (node->id < 0x800) #endif - this->follow(node); + follow(node); } // If the message arrived from our uplink, track that we're still linked. - if (node->id == this->header.uplinkId) { - this->uplinkTimer.start(UPLINK_TIMEOUT); + if (node->id == header.uplinkId) { + uplinkTimer.start(UPLINK_TIMEOUT); } // If a message indicates that another node is following this one, or // should be (it's not following anything, but this node's ID is higher) // enter or continue re-broadcasting mode. - if (node->uplinkId == this->header.id - || (node->uplinkId == 0 && node->id < this->header.id)) { + if (node->uplinkId == header.id + || (node->uplinkId == 0 && node->id < header.id)) { Serial.printf(" %03X/%03X is following me\n", node->id, node->uplinkId); - this->rebroadcastTimer.start(REBROADCAST_TIME); + rebroadcastTimer.start(REBROADCAST_TIME); } } @@ -212,7 +214,7 @@ class LightNode { NodeMessage* message = (NodeMessage*)data; // Ignore this message if it's the wrong version. - if (message->header.version != this->header.version) { + if (message->header.version != header.version) { #ifdef NODE_DEBUGGING Serial.print(" -- !version "); print_message(message, rssi); @@ -222,18 +224,18 @@ class LightNode { } // Track that another node exists, updating this node's understanding of the mesh. - this->onPeerPing(&message->header); + onPeerPing(&message->header); bool ignore = false; switch (message->recipients) { case RECIPIENTS_ALL: // Ignore this message if not from the uplink - ignore = (message->header.id != this->header.uplinkId); + ignore = (message->header.id != header.uplinkId); break; case RECIPIENTS_ROOT: // Ignore this message if not from one of this node's downlinks - ignore = (message->header.uplinkId != this->header.id); + ignore = (message->header.uplinkId != header.id); break; case RECIPIENTS_INFO: @@ -256,7 +258,7 @@ class LightNode { } // Execute the received command - if (message->recipients != RECIPIENTS_ROOT || !this->is_following()) { + if (message->recipients != RECIPIENTS_ROOT || !is_following()) { Serial.print(" >> "); print_message(message, rssi); Serial.print(" "); @@ -269,7 +271,7 @@ class LightNode { strip.timebase = new_timebase; // Execute the command - auto valid = this->receiver->onCommand( + auto valid = receiver->onCommand( message->command, &message->data ); @@ -280,17 +282,17 @@ class LightNode { } // Re-broadcast the message if appropriate - if (!this->rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { - message->header = this->header; - if (!this->is_following()) + if (!rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { + message->header = header; + if (!is_following()) message->recipients = RECIPIENTS_ALL; - this->broadcast(message, true); + broadcastMessage(message, true); } } - void broadcast(NodeMessage *message, bool is_rebroadcast=false) { + void broadcastMessage(NodeMessage *message, bool is_rebroadcast=false) { // Don't broadcast anything if this node isn't active. - if (this->status != NODE_STATUS_STARTED) + if (status != NODE_STATUS_STARTED) return; message->timebase = strip.timebase + millis(); @@ -321,7 +323,7 @@ class LightNode { message.recipients = RECIPIENTS_INFO; } else if (command == COMMAND_STATE) { message.recipients = RECIPIENTS_ALL; - } else if (this->is_following()) { + } else if (is_following()) { // Follower nodes must request that the root re-sends this message message.recipients = RECIPIENTS_ROOT; } else { @@ -329,16 +331,16 @@ class LightNode { } message.command = command; memcpy(&message.data, data, len); - this->broadcast(&message); + broadcastMessage(&message); } void setup() { #ifdef NODE_DEBUGGING - this->reset(TESTING_NODE_ID); + reset(TESTING_NODE_ID); #else - this->reset(); + reset(); #endif - this->statusTimer.stop(); + statusTimer.stop(); quickEspNow.onDataRcvd(onDataReceived); Serial.println("Mesh: ok"); @@ -346,45 +348,45 @@ class LightNode { void update() { // Check the last time we heard from the uplink node - if (is_following() && this->uplinkTimer.ended()) { - this->follow(NULL); + if (is_following() && uplinkTimer.ended()) { + follow(NULL); } - if (this->statusTimer.every(WIFI_CHECK_RATE)) { + if (statusTimer.every(WIFI_CHECK_RATE)) { // The broadcast timer doubles as a timer for startup delay // Once the initial timer has ended, mark this node as started - if (this->status == NODE_STATUS_STARTING) - this->status = NODE_STATUS_STARTED; + if (status == NODE_STATUS_STARTING) + status = NODE_STATUS_STARTED; // Check WiFi status and update node status if wifi changed if (WiFi.isConnected()) - this->onWifiConnect(); + onWifiConnect(); else - this->onWifiDisconnect(); + onWifiDisconnect(); } } void reset(MeshId id = 0) { if (id == 0) id = random(256, 4000); // Leave room at bottom and top of 12 bits - this->header.id = id; - this->follow(NULL); + header.id = id; + follow(NULL); } void follow(MeshNodeHeader* node) { if (node == NULL) { - if (this->header.uplinkId != 0) { + if (header.uplinkId != 0) { Serial.println("Uplink lost"); } // Unfollow: following zero means you have no uplink - this->header.uplinkId = 0; - this->onMeshChange(); + header.uplinkId = 0; + onMeshChange(); return; } // Already following? ignore - if (this->header.uplinkId == node->id) + if (header.uplinkId == node->id) return; // Follow @@ -392,17 +394,46 @@ class LightNode { node->id, node->uplinkId ); - this->header.uplinkId = node->id; - this->onMeshChange(); + header.uplinkId = node->id; + onMeshChange(); } bool is_following() { - return this->header.uplinkId != 0; + return header.uplinkId != 0; } }; +typedef struct wizmote_message { + uint8_t program; // 0x91 for ON button, 0x81 for all others + uint8_t seq[4]; // Incremetal sequence number 32 bit unsigned integer LSB first + uint8_t byte5 = 32; // Unknown + uint8_t button; // Identifies which button is being pressed + uint8_t byte8 = 1; // Unknown, but always 0x01 + uint8_t byte9 = 100; // Unnkown, but always 0x64 + + uint8_t byte10; // Unknown, maybe checksum + uint8_t byte11; // Unknown, maybe checksum + uint8_t byte12; // Unknown, maybe checksum + uint8_t byte13; // Unknown, maybe checksum +} wizmote_message; + +void onWizmote(uint8_t* address, wizmote_message* data, uint8_t len) { + // First make sure this is a WizMote message. + if (len != sizeof(wizmote_message) || data->byte8 != 1 || data->byte9 != 100 || data->byte5 != 32) + return; + + static uint32_t last_seq = 0; + uint32_t cur_seq = data->seq[0] | (data->seq[1] << 8) | (data->seq[2] << 16) | (data->seq[3] << 24); + if (cur_seq == last_seq) + return; + last_seq = cur_seq; + + LightNode::instance->receiver->onButton(data->button); +} + LightNode* LightNode::instance = nullptr; -void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { +void onDataReceived(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { LightNode::instance->onPeerData(address, data, len, rssi, broadcast); + onWizmote(address, (wizmote_message*)data, len); } diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 769c172a19..6daac941f8 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -25,15 +25,11 @@ typedef enum Energy: uint8_t { typedef struct ControlParameters { + ControlParameters(Duration d=MediumDuration, Energy e=Chill) : duration(d), energy(e) {}; + public: Duration duration=MediumDuration; Energy energy=Chill; - - ControlParameters(Duration duration=MediumDuration, Energy energy=Chill) { - this->duration=duration; - this->energy=energy; - }; - } ControlParams; typedef enum PenMode: uint8_t { @@ -72,15 +68,15 @@ typedef enum BeatPulse: uint8_t { class EffectParameters { public: + EffectParameters(EffectMode e=None, PenMode p=Draw, BeatPulse b=Beat, uint8_t c=255) : + effect(e), + pen(p), + beat(b), + chance(c) + { }; + EffectMode effect; PenMode pen=Draw; BeatPulse beat=Beat; uint8_t chance=255; - - EffectParameters(EffectMode effect=None, PenMode pen=Draw, BeatPulse beat=Beat, uint8_t chance=255) { - this->effect=effect; - this->pen=pen; - this->beat=beat; - this->chance=chance; - }; }; diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index 2db9189e61..00ad72a304 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -20,51 +20,53 @@ extern void drawFlash(Particle *particle, WS2812FX* leds); class Particle { public: + Particle(uint16_t pos, CRGB c=CRGB::White, PenMode p=Draw, uint32_t life=20000, ParticleFn fn=drawPoint) : + age(0), + color(c), + brightness(192<<8), + drawFn(fn), + velocity(0), + gravity(0) + { + pen = p; + position = pos; + lifetime = life; + }; + BeatFrame_24_8 born; BeatFrame_24_8 lifetime; BeatFrame_24_8 age; - uint16_t position = 0; - int16_t velocity = 0; - int16_t gravity = 0; + CRGB color; + PenMode pen; + uint16_t brightness; + ParticleFn drawFn; + + uint16_t position; + int16_t velocity; + int16_t gravity; void (*die_fn)(Particle *particle) = NULL; - PenMode pen = Draw; #ifdef PARTICLE_PALETTES CRGBPalette16 palette; // 48 bytes per particle!? #endif - CRGB color; - uint16_t brightness; - ParticleFn drawFn; - - Particle(uint16_t position, CRGB color=CRGB::White, PenMode pen=Draw, uint32_t lifetime=20000, ParticleFn drawFn=drawPoint) - { - this->age = 0; - this->position = position; - this->color = color; - this->pen = pen; - this->lifetime = lifetime; - this->brightness = (192<<8); - this->drawFn = drawFn; - } - void update(BeatFrame_24_8 frame) { // Particles get brighter with the beat - this->brightness = (scale8(particleVolume, 80) + 170) << 8; + brightness = (scale8(particleVolume, 80) + 170) << 8; - this->age = frame - this->born; - this->position = this->udelta16(this->position, this->velocity); - this->velocity = this->delta16(this->velocity, this->gravity); + age = frame - born; + position = udelta16(position, velocity); + velocity = delta16(velocity, gravity); } uint16_t age_frac16(BeatFrame_24_8 age) { - if (age >= this->lifetime) + if (age >= lifetime) return 65535; uint32_t a = age * 65536; - return a / this->lifetime; + return a / lifetime; } uint16_t udelta16(uint16_t x, int16_t dx) @@ -88,17 +90,17 @@ class Particle { CRGB color_at(uint16_t age_frac) { // Particles get dimmer with age uint8_t a = age_frac >> 8; - uint8_t brightness = scale8((uint8_t)(this->brightness>>8), 255-a); + uint8_t brightness = scale8((uint8_t)(brightness>>8), 255-a); #ifdef PARTICLE_PALETTES // a black pattern actually means to use the current palette - if (this->color == CRGB(0,0,0)) - return ColorFromPalette(this->palette, a, brightness); + if (color == CRGB(0,0,0)) + return ColorFromPalette(palette, a, brightness); #endif - uint8_t r = scale8(this->color.r, brightness); - uint8_t g = scale8(this->color.g, brightness); - uint8_t b = scale8(this->color.b, brightness); + uint8_t r = scale8(color.r, brightness); + uint8_t g = scale8(color.g, brightness); + uint8_t b = scale8(color.b, brightness); return CRGB(r,g,b); } @@ -106,7 +108,7 @@ class Particle { CRGB c = CRGB(strip.getPixelColor(pos)); CRGB new_color; - switch (this->pen) { + switch (pen) { case Draw: new_color = color; break; diff --git a/usermods/Tubes/sound.h b/usermods/Tubes/sound.h index 02280170c5..facd751577 100644 --- a/usermods/Tubes/sound.h +++ b/usermods/Tubes/sound.h @@ -14,30 +14,30 @@ class Sounder { } void update() { - if (!this->active) { + if (!active) { particleVolume = DEFAULT_PARTICLE_VOLUME; // Average volume return; } um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - this->active = false; - this->overlay = false; + active = false; + overlay = false; return; } float volumeSmth = *(float*)um_data->u_data[0]; - this->volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. - particleVolume = this->volume; + volume = constrain(volumeSmth, 0, 255); // Keep the sample from overflowing. + particleVolume = volume; } void handleOverlayDraw() { - if (!this->active) + if (!active) return; - if (!this->overlay) + if (!overlay) return; - int len = scale8(this->volume, 32); + int len = scale8(volume, 32); Segment& segment = strip.getMainSegment(); diff --git a/usermods/Tubes/timer.h b/usermods/Tubes/timer.h index 22b00ab482..87b5de71b3 100644 --- a/usermods/Tubes/timer.h +++ b/usermods/Tubes/timer.h @@ -11,19 +11,19 @@ class GlobalTimer { void setup() { - this->last_millis = this->now_millis = millis(); - this->last_micros = this->now_micros = micros(); + last_millis = now_millis = millis(); + last_micros = now_micros = micros(); } void update() { - this->last_millis = this->now_millis; - this->now_millis = millis(); - this->delta_millis = this->now_millis - this->last_millis; + last_millis = now_millis; + now_millis = millis(); + delta_millis = now_millis - last_millis; - this->last_micros = this->now_micros; - this->now_micros = micros(); - this->delta_micros = this->now_micros - this->last_micros; + last_micros = now_micros; + now_micros = micros(); + delta_micros = now_micros - last_micros; } }; @@ -36,32 +36,32 @@ class Timer { uint32_t markTime; void start(uint32_t duration_ms) { - this->markTime = globalTimer.now_millis + duration_ms; + markTime = globalTimer.now_millis + duration_ms; } void stop() { - this->start(0); + start(0); } uint32_t since_mark() { - if (globalTimer.now_millis < this->markTime) + if (globalTimer.now_millis < markTime) return 0; - return globalTimer.now_millis - this->markTime; + return globalTimer.now_millis - markTime; } void snooze(uint32_t duration_ms) { - while (this->markTime < globalTimer.now_millis) - this->markTime += duration_ms; + while (markTime < globalTimer.now_millis) + markTime += duration_ms; } bool ended() { - return globalTimer.now_millis > this->markTime; + return globalTimer.now_millis > markTime; } bool every(uint32_t duration_ms) { - if (!this->ended()) + if (!ended()) return 0; - this->snooze(duration_ms); + snooze(duration_ms); return 1; } }; \ No newline at end of file diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index e227610de0..2c8fa72e8e 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -62,49 +62,48 @@ class VirtualStrip { bool beat_pulse; int bps = 0; - VirtualStrip(uint8_t num_leds) + VirtualStrip(uint8_t num) : num_leds(num) { - this->fade = Dead; - this->num_leds = num_leds; + fade = Dead; } - void load(Background &background, uint8_t fade_speed=DEFAULT_FADE_SPEED) + void load(Background &b, uint8_t fs=DEFAULT_FADE_SPEED) { - this->background = background; - this->fade = FadeIn; - this->fader = 0; - this->fade_speed = fade_speed; - this->brightness = DEF_BRIGHT; + background = b; + fade = FadeIn; + fader = 0; + fade_speed = fs; + brightness = DEF_BRIGHT; } bool isWled() { - return this->background.wled_fx_id != 0; + return background.wled_fx_id != 0; } - void fadeOut(uint8_t fade_speed=DEFAULT_FADE_SPEED) + void fadeOut(uint8_t fs=DEFAULT_FADE_SPEED) { - if (this->fade == Dead) + if (fade == Dead) return; - this->fade = FadeOut; - this->fade_speed = fade_speed; + fade = FadeOut; + fade_speed = fs; } void darken(uint8_t amount=10) { - fadeToBlackBy( this->leds, this->num_leds, amount); + fadeToBlackBy( leds, num_leds, amount); } void fill(CRGB crgb) { - fill_solid( this->leds, this->num_leds, crgb); + fill_solid( leds, num_leds, crgb); } - void update(BeatFrame_24_8 frame, uint8_t beat_pulse) + void update(BeatFrame_24_8 fr, uint8_t bp) { - if (this->fade == Dead) + if (fade == Dead) return; - this->frame = frame; + frame = fr; switch (this->background.sync) { case All: @@ -112,52 +111,52 @@ class VirtualStrip { case SinDrift: // Drift slightly - this->frame = frame + (beatsin16( 5 ) >> 6); + frame = frame + (beatsin16( 5 ) >> 6); break; case Swing: // Swing the beat - this->frame = swing(frame); + frame = swing(frame); break; case SwingDrift: // Swing the beat AND drift slightly - this->frame = swing(frame) + (beatsin16( 5 ) >> 6); + frame = swing(frame) + (beatsin16( 5 ) >> 6); break; case Pulse: // Pulsing from 30 - 210 brightness - this->brightness = scale8(beatsin8( 10 ), 180) + 30; + brightness = scale8(beatsin8( 10 ), 180) + 30; break; } - this->hue = (this->frame >> 4) % 256; - this->beat = (this->frame >> 8) % 16; - this->beat_pulse = beat_pulse; + hue = (frame >> 4) % 256; + beat = (frame >> 8) % 16; + beat_pulse = bp; // Animate this virtual strip - this->background.animate(this); + background.animate(this); - switch (this->fade) { + switch (fade) { case Steady: case Dead: break; case FadeIn: - if (65535 - this->fader < this->fade_speed) { - this->fader = 65535; - this->fade = Steady; + if (65535 - fader < fade_speed) { + fader = 65535; + fade = Steady; } else { - this->fader += this->fade_speed; + fader += fade_speed; } break; case FadeOut: - if (this->fader < this->fade_speed) { - this->fader = 0; - this->fade = Dead; + if (fader < fade_speed) { + fader = 0; + fade = Dead; return; } else { - this->fader -= this->fade_speed; + fader -= fade_speed; } break; } @@ -170,25 +169,25 @@ class VirtualStrip { } CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { - return CHSV(this->hue + offset, saturation, value); + return CHSV(hue + offset, saturation, value); } - void blend(CRGB output[], uint8_t num_leds, uint8_t brightness, bool overwrite=0) { - if (this->fade == Dead) + void blend(CRGB output[], uint8_t num_leds, uint8_t br, bool overwrite=0) { + if (fade == Dead) return; // WLED is blended in elsewhere - if (this->isWled()) + if (isWled()) return; - brightness = scale8(this->brightness, brightness); + br = scale8(brightness, br); for (unsigned i=0; i < num_leds; i++) { uint8_t pos = i; - CRGB c = this->leds[pos]; + CRGB c = leds[pos]; nscale8x3(c.r, c.g, c.b, brightness); - nscale8x3(c.r, c.g, c.b, this->fader>>8); + nscale8x3(c.r, c.g, c.b, fader>>8); if (overwrite) output[i] = c; else @@ -198,12 +197,12 @@ class VirtualStrip { uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) { - return scaled16to8(sin16( this->frame << 7 ) + 32768, lowest, highest); + return scaled16to8(sin16( frame << 7 ) + 32768, lowest, highest); } uint8_t bpm_cos16( uint16_t lowest=0, uint16_t highest=65535 ) { - return scaled16to8(cos16( this->frame << 7 ) + 32768, lowest, highest); + return scaled16to8(cos16( frame << 7 ) + 32768, lowest, highest); } }; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b962bb5828..14f5e6d1e3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -604,11 +604,6 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0"; * Dissolve function */ uint16_t dissolve(uint32_t color) { - //bool wa = (SEGCOLOR(1) != 0 && strip.getBrightness() < 255); //workaround, can't compare getPixel to color if not full brightness - if (SEGENV.call == 0) { - SEGMENT.fill(SEGCOLOR(1)); - } - for (int j = 0; j <= SEGLEN / 15; j++) { if (random8() <= SEGMENT.intensity) { for (size_t times = 0; times < 10; times++) //attempt to spawn a new pixel 10 times @@ -764,7 +759,7 @@ uint16_t mode_android(void) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } - if (SEGENV.aux1 > ((float)SEGMENT.intensity/255.0)*(float)SEGLEN) + if (SEGENV.aux1 > (SEGMENT.intensity*SEGLEN)/255) { SEGENV.aux0 = 1; } else @@ -814,7 +809,7 @@ static const char _data_FX_MODE_ANDROID[] PROGMEM = "Android@!,Width;!,!;!;;m12= */ uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); - uint16_t a = counter * SEGLEN >> 16; + uint16_t a = (counter * SEGLEN) >> 16; bool chase_random = (SEGMENT.mode == FX_MODE_CHASE_RANDOM); if (chase_random) { @@ -828,7 +823,7 @@ uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palett SEGENV.step = a; // Use intensity setting to vary chase up to 1/2 string length - uint8_t size = 1 + (SEGMENT.intensity * SEGLEN >> 10); + uint8_t size = 1 + ((SEGMENT.intensity * SEGLEN) >> 10); uint16_t b = a + size; //"trail" of chase, filled with color1 if (b > SEGLEN) b -= SEGLEN; @@ -1116,8 +1111,9 @@ static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;; uint16_t larson_scanner(bool dual) { + if (SEGLEN == 1) return mode_static(); uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); - uint16_t index = counter * SEGLEN >> 16; + uint16_t index = (counter * SEGLEN) >> 16; SEGMENT.fade_out(SEGMENT.intensity); @@ -1204,7 +1200,6 @@ uint16_t mode_fireworks() { const uint16_t height = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { - SEGMENT.fill(SEGCOLOR(1)); SEGENV.aux0 = UINT16_MAX; SEGENV.aux1 = UINT16_MAX; } @@ -1305,24 +1300,23 @@ static const char _data_FX_MODE_FIRE_FLICKER[] PROGMEM = "Fire Flicker@!,!;!;!;0 * Gradient run base function */ uint16_t gradient_base(bool loading) { + if (SEGLEN == 1) return mode_static(); uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); - uint16_t pp = counter * SEGLEN >> 16; + uint16_t pp = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) pp = 0; - float val; //0.0 = sec 1.0 = pri - float brd = loading ? SEGMENT.intensity : SEGMENT.intensity/2; - if (brd <1.0) brd = 1.0; + int val; //0 = sec 1 = pri + int brd = 1 + loading ? SEGMENT.intensity/2 : SEGMENT.intensity/4; + //if (brd < 1) brd = 1; int p1 = pp-SEGLEN; int p2 = pp+SEGLEN; - for (int i = 0; i < SEGLEN; i++) - { - if (loading) - { - val = abs(((i>pp) ? p2:pp) -i); + for (int i = 0; i < SEGLEN; i++) { + if (loading) { + val = abs(((i>pp) ? p2:pp) - i); } else { - val = MIN(abs(pp-i),MIN(abs(p1-i),abs(p2-i))); + val = min(abs(pp-i),min(abs(p1-i),abs(p2-i))); } - val = (brd > val) ? val/brd * 255 : 255; + val = (brd > val) ? (val * 255) / brd : 255; SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(0), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), val)); } @@ -1903,12 +1897,8 @@ static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; //eight colored dots, weaving in and out of sync with each other uint16_t mode_juggle(void) { if (SEGLEN == 1) return mode_static(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } SEGMENT.fadeToBlackBy(192 - (3*SEGMENT.intensity/4)); - CRGB fastled_col; byte dothue = 0; for (int i = 0; i < 8; i++) { @@ -2073,12 +2063,9 @@ static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!"; // colored stripes pulsing at a defined Beats-Per-Minute (BPM) uint16_t mode_bpm() { - //CRGB fastled_col; uint32_t stp = (strip.now / 20) & 0xFF; uint8_t beat = beatsin8(SEGMENT.speed, 64, 255); for (int i = 0; i < SEGLEN; i++) { - //fastled_col = ColorFromPalette(SEGPALETTE, stp + (i * 2), beat - stp + (i * 10)); - //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(stp + (i * 2), false, PALETTE_SOLID_WRAP, 0, beat - stp + (i * 10))); } @@ -2475,12 +2462,6 @@ static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Wav // // TwinkleFOX: Twinkling 'holiday' lights that fade in and out. // Colors are chosen from a palette. Read more about this effect using the link above! - -// If COOL_LIKE_INCANDESCENT is set to 1, colors will -// fade out slighted 'reddened', similar to how -// incandescent bulbs change color as they get dim down. -#define COOL_LIKE_INCANDESCENT 1 - CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) @@ -2519,7 +2500,7 @@ CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) CRGB c; if (bright > 0) { c = ColorFromPalette(SEGPALETTE, hue, bright, NOBLEND); - if(COOL_LIKE_INCANDESCENT == 1) { + if (!SEGMENT.check1) { // This code takes a pixel, and if its in the 'fading down' // part of the cycle, it adjusts the color a little bit like the // way that incandescent bulbs fade toward 'red' as they dim. @@ -2605,14 +2586,14 @@ uint16_t mode_twinklefox() { return twinklefox_base(false); } -static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate;;!"; +static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate,,,,Cool;;!"; uint16_t mode_twinklecat() { return twinklefox_base(true); } -static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate;;!"; +static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool;;!"; //inspired by https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectBlinkingHalloweenEyes @@ -2930,7 +2911,7 @@ uint16_t mode_glitter() static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;1,2,Glitter color;!;;pal=0,m12=0"; //pixels -//Solid colour background with glitter +//Solid colour background with glitter (can be replaced by Glitter) uint16_t mode_solid_glitter() { SEGMENT.fill(SEGCOLOR(0)); @@ -3020,8 +3001,7 @@ static const char _data_FX_MODE_POPCORN[] PROGMEM = "Popcorn@!,!,,,,,Overlay;!,! uint16_t candle(bool multi) { - if (multi) - { + if (multi && SEGLEN > 1) { //allocate segment data uint16_t dataSize = max(1, SEGLEN -1) *3; //max. 1365 pixels (ESP8266) if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed @@ -3590,8 +3570,8 @@ uint16_t mode_percent(void) { uint8_t percent = SEGMENT.intensity; percent = constrain(percent, 0, 200); - uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 - : SEGLEN * (200 - percent) / 100.0; + uint16_t active_leds = (percent < 100) ? roundf(SEGLEN * percent / 100.0f) + : roundf(SEGLEN * (200 - percent) / 100.0f); uint8_t size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); if (SEGMENT.speed == 255) size = 255; @@ -4582,11 +4562,6 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma const uint16_t rows = SEGMENT.virtualHeight(); uint16_t x, y; - // initialize on first call - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = millis()/128; // timebase // outer stars @@ -4621,7 +4596,6 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so const uint16_t rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); SEGENV.aux0 = 0; // start with red hue } @@ -4673,12 +4647,7 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(64); - for (int i = 0; i < cols; i++) { SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+millis()/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+millis()/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); @@ -4744,12 +4713,7 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(128); - const uint16_t maxDim = MAX(cols, rows)/2; unsigned long t = millis() / (32 - (SEGMENT.speed>>3)); unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup @@ -4809,10 +4773,6 @@ uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.so const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(16); for (size_t i = 8; i > 0; i--) { SEGMENT.addPixelColorXY(beatsin8(SEGMENT.speed/8 + i, 0, cols - 1), @@ -5256,12 +5216,7 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); - uint_fast32_t t = (millis() * 8) / (256 - SEGMENT.speed); // optimized to avoid float for (int i = 0; i < cols; i++) { uint16_t thisVal = inoise8(i * 30, t, t); @@ -5352,12 +5307,7 @@ uint16_t mode_2DPulser(void) { // By: ldirko https://edi const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); - uint32_t a = strip.now / (18 - SEGMENT.speed / 16); uint16_t x = (a / 14) % cols; uint16_t y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); @@ -5410,14 +5360,9 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - const uint8_t kBorderWidth = 2; SEGMENT.fadeToBlackBy(24); - uint8_t blurAmount = SEGMENT.custom3>>1; // reduced resolution slider SEGMENT.blur(blurAmount); @@ -5538,10 +5483,6 @@ uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [ht const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - uint32_t tb = strip.now >> 12; // every ~4s if (tb > SEGENV.step) { int8_t dir = ++SEGENV.aux0; @@ -5607,7 +5548,6 @@ uint16_t mode_2Dcrazybees(void) { bee_t *bee = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); for (size_t i = 0; i < n; i++) { bee[i].posX = random8(0, cols); bee[i].posY = random8(0, rows); @@ -5616,7 +5556,7 @@ uint16_t mode_2Dcrazybees(void) { } if (millis() > SEGENV.step) { - SEGENV.step = millis() + (FRAMETIME * 8 / ((SEGMENT.speed>>5)+1)); + SEGENV.step = millis() + (FRAMETIME * 16 / ((SEGMENT.speed>>4)+1)); SEGMENT.fadeToBlackBy(32); @@ -5679,9 +5619,9 @@ uint16_t mode_2Dghostrider(void) { if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { SEGENV.aux0 = cols; SEGENV.aux1 = rows; - SEGMENT.fill(BLACK); random16_set_seed(strip.now); lighter->angleSpeed = random8(0,20) - 10; + lighter->gAngle = random16(); lighter->Vspeed = 5; lighter->gPosX = (cols/2) * 10; lighter->gPosY = (rows/2) * 10; @@ -5689,6 +5629,7 @@ uint16_t mode_2Dghostrider(void) { lighter->lightersPosX[i] = lighter->gPosX; lighter->lightersPosY[i] = lighter->gPosY + i; lighter->time[i] = i * 2; + lighter->reg[i] = false; } } @@ -5763,7 +5704,7 @@ uint16_t mode_2Dfloatingblobs(void) { if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { SEGENV.aux0 = cols; // re-initialise if virtual size changes SEGENV.aux1 = rows; - SEGMENT.fill(BLACK); + //SEGMENT.fill(BLACK); for (size_t i = 0; i < MAX_BLOBS; i++) { blob->r[i] = random8(1, cols>8 ? (cols/4) : 2); blob->sX[i] = (float) random8(3, cols) / (float)(256 - SEGMENT.speed); // speed x @@ -5777,7 +5718,7 @@ uint16_t mode_2Dfloatingblobs(void) { } } - SEGMENT.fadeToBlackBy(20); + SEGMENT.fadeToBlackBy((SEGMENT.custom2>>3)+1); // Bounce balls around for (size_t i = 0; i < Amount; i++) { @@ -5833,7 +5774,7 @@ uint16_t mode_2Dfloatingblobs(void) { return FRAMETIME; } #undef MAX_BLOBS -static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur;!;!;2;c1=8"; +static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;!;!;2;c1=8"; //////////////////////////// @@ -5924,10 +5865,6 @@ uint16_t mode_2Ddriftrose(void) { const float CY = (rows-rows%2)/2.f - .5f; const float L = min(cols, rows) / 2.f; - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); for (size_t i = 1; i < 37; i++) { uint32_t x = (CX + (sin_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; @@ -6100,8 +6037,6 @@ uint16_t mode_2DSwirl(void) { float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg??? int16_t volumeRaw = *(int16_t*) um_data->u_data[1]; - // printUmData(); - SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (ms / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (ms / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); SEGMENT.addPixelColorXY(ni,nj, ColorFromPalette(SEGPALETTE, (ms / 17 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 17, 200, 255); @@ -6124,10 +6059,6 @@ uint16_t mode_2DWaverly(void) { const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - if (SEGENV.call == 0) { - SEGMENT.fill(BLACK); - } - um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6175,6 +6106,7 @@ typedef struct Gravity { // * GRAVCENTER // /////////////////////// uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); const uint16_t dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -6193,7 +6125,7 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0); // map to pixels available in current segment + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment uint16_t tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -6223,6 +6155,7 @@ static const char _data_FX_MODE_GRAVCENTER[] PROGMEM = "Gravcenter@Rate of fall, // * GRAVCENTRIC // /////////////////////// uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); uint16_t dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -6241,10 +6174,10 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew //SEGMENT.fade_out(240); // twice? really? SEGMENT.fade_out(253); // 50% - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0); // map to pixels availeable in current segment + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0f); // map to pixels availeable in current segment int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -6274,6 +6207,7 @@ static const char _data_FX_MODE_GRAVCENTRIC[] PROGMEM = "Gravcentric@Rate of fal // * GRAVIMETER // /////////////////////// uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); uint16_t dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -6320,6 +6254,7 @@ static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall, // * JUGGLES // ////////////////////// uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6343,6 +6278,7 @@ static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!; // * MATRIPIX // ////////////////////// uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment um_data_t *um_data; @@ -6374,6 +6310,7 @@ static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness;!,!; // * MIDNOISE // ////////////////////// uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); // Changing xdist to SEGENV.aux0 and ydist to SEGENV.aux1. um_data_t *um_data; @@ -6477,6 +6414,7 @@ static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Wid // * PIXELWAVE // ////////////////////// uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment if (SEGENV.call == 0) { @@ -6552,6 +6490,7 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels /////////////////////// // Andrew's crappy peak detector. If I were 40+ years younger, I'd learn signal processing. uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); uint16_t size = 0; uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); @@ -6595,6 +6534,7 @@ static const char _data_FX_MODE_PUDDLEPEAK[] PROGMEM = "Puddlepeak@Fade rate,Pud // * PUDDLES // ////////////////////// uint16_t mode_puddles(void) { // Puddles. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); uint16_t size = 0; uint8_t fadeVal = map(SEGMENT.speed, 0, 255, 224, 254); uint16_t pos = random16(SEGLEN); // Set a random starting position. @@ -6626,6 +6566,7 @@ static const char _data_FX_MODE_PUDDLES[] PROGMEM = "Puddles@Fade rate,Puddle si // * PIXELS // ////////////////////// uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); if (!SEGENV.allocateData(32*sizeof(uint8_t))) return mode_static(); //allocation failed uint8_t *myVals = reinterpret_cast(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. @@ -6659,6 +6600,7 @@ static const char _data_FX_MODE_PIXELS[] PROGMEM = "Pixels@Fade rate,# of pixels // ** Blurz // ////////////////////// uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment um_data_t *um_data; @@ -6695,6 +6637,7 @@ static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color // ** DJLight // ///////////////////////// uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. + if (SEGLEN == 1) return mode_static(); const int mid = SEGLEN / 2; um_data_t *um_data; @@ -6728,6 +6671,7 @@ static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;1f;m12=2,s // ** Freqmap // //////////////////// uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. + if (SEGLEN == 1) return mode_static(); // Start frequency = 60 Hz and log10(60) = 1.78 // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 @@ -6764,6 +6708,7 @@ static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting // ** Freqmatrix // /////////////////////// uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6829,24 +6774,27 @@ uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. } float FFT_MajorPeak = *(float*)um_data->u_data[4]; float my_magnitude = *(float*)um_data->u_data[5] / 16.0f; - if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1.0f; // log10(0) is "forbidden" (throws exception) - uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. + // this code translates to speed * (2 - speed/255) which is a) speed*2 or b) speed (when speed is 255) + // and since fade_out() can only take 0-255 it will behave incorrectly when speed > 127 + //uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. + uint16_t fadeRate = SEGMENT.speed*SEGMENT.speed; // Get to 255 as quick as you can. + fadeRate = map(fadeRate, 0, 65535, 1, 255); - if (SEGENV.call == 0) SEGMENT.fill(BLACK); int fadeoutDelay = (256 - SEGMENT.speed) / 64; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(fadeRate); + uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow for (int i=0; i < SEGMENT.intensity/32+1; i++) { uint16_t locn = random16(0,SEGLEN); - uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. - if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); } return FRAMETIME; } // mode_freqpixels() -static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting color and # of pixels;;;1f;m12=0,si=0"; // Pixels, Beatsin +static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting color and # of pixels;!,!,;!;1f;m12=0,si=0"; // Pixels, Beatsin ////////////////////// @@ -6865,6 +6813,7 @@ static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Sta // As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. // Depending on the music stream you have you might find it useful to change the frequency mapping. uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6921,7 +6870,7 @@ static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effec // ** Gravfreq // /////////////////////// uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. - + if (SEGLEN == 1) return mode_static(); uint16_t dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Gravity* gravcen = reinterpret_cast(SEGENV.data); @@ -6938,9 +6887,9 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. SEGMENT.fade_out(250); float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling + segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling - float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0); // map to pixels availeable in current segment + float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0f); // map to pixels availeable in current segment int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -6973,6 +6922,7 @@ static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sens // ** Noisemove // ////////////////////// uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6980,8 +6930,6 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli } uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; - if (SEGENV.call == 0) SEGMENT.fill(BLACK); - //SEGMENT.fade_out(224); // Just in case something doesn't get faded. int fadeoutDelay = (256 - SEGMENT.speed) / 96; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(4+ SEGMENT.speed/4); @@ -7001,6 +6949,7 @@ static const char _data_FX_MODE_NOISEMOVE[] PROGMEM = "Noisemove@Speed of perlin // ** Rocktaves // ////////////////////// uint16_t mode_rocktaves(void) { // Rocktaves. Same note from each octave is same colour. By: Andrew Tuline + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -7009,8 +6958,7 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac float FFT_MajorPeak = *(float*) um_data->u_data[4]; float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; - if (SEGENV.call == 0) SEGMENT.fill(BLACK); - SEGMENT.fadeToBlackBy(16); // Just in case something doesn't get faded. + SEGMENT.fadeToBlackBy(16); // Just in case something doesn't get faded. float frTemp = FFT_MajorPeak; uint8_t octCount = 0; // Octave counter. @@ -7025,8 +6973,8 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac frTemp = frTemp/2; } - frTemp -=132; // This should give us a base musical note of C3 - frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255; + frTemp -= 132.0f; // This should give us a base musical note of C3 + frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255; uint16_t i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); i = constrain(i, 0, SEGLEN-1); @@ -7042,7 +6990,7 @@ static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;1f;m12=1 /////////////////////// // Combines peak detection with FFT_MajorPeak and FFT_Magnitude. uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline - if (SEGENV.call == 0) SEGMENT.fill(BLACK); + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -7117,7 +7065,6 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. rippleTime = true; } - if (SEGENV.call == 0) SEGMENT.fill(BLACK); int fadeoutDelay = (256 - SEGMENT.speed) / 64; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(SEGMENT.speed); diff --git a/wled00/FX.h b/wled00/FX.h index a1627a29bf..56323f15e9 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -381,6 +381,26 @@ typedef struct Segment { byte *data; // effect data pointer static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions) + typedef struct TemporarySegmentData { + uint16_t _optionsT; + uint32_t _colorT[NUM_COLORS]; + uint8_t _speedT; + uint8_t _intensityT; + uint8_t _custom1T, _custom2T; // custom FX parameters/sliders + struct { + uint8_t _custom3T : 5; // reduced range slider (0-31) + bool _check1T : 1; // checkmark 1 + bool _check2T : 1; // checkmark 2 + bool _check3T : 1; // checkmark 3 + }; + uint16_t _aux0T; + uint16_t _aux1T; + uint32_t _stepT; + uint32_t _callT; + uint8_t *_dataT; + uint16_t _dataLenT; + } tmpsegd_t; + private: union { uint8_t _capabilities; @@ -396,43 +416,27 @@ typedef struct Segment { static uint16_t _usedSegmentData; // perhaps this should be per segment, not static - static CRGBPalette16 _randomPalette; - static CRGBPalette16 _newRandomPalette; - static unsigned long _lastPaletteChange; + static CRGBPalette16 _randomPalette; // actual random palette + static CRGBPalette16 _newRandomPalette; // target random palette + static unsigned long _lastPaletteChange; // last random palette change time in millis() + static bool _modeBlend; // mode/effect blending semaphore // transition data, valid only if transitional==true, holds values during transition (72 bytes) struct Transition { - uint32_t _colorT[NUM_COLORS]; + tmpsegd_t _segT; // previous segment environment uint8_t _briT; // temporary brightness uint8_t _cctT; // temporary CCT + uint8_t _modeT; // previous mode/effect CRGBPalette16 _palT; // temporary palette uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 belnds possible) - uint8_t _modeP; // previous mode/effect - //uint16_t _aux0, _aux1; // previous mode/effect runtime data - //uint32_t _step, _call; // previous mode/effect runtime data - //byte *_data; // previous mode/effect runtime data - unsigned long _start; // must accommodate millis() + unsigned long _start; // must accommodate millis() uint16_t _dur; Transition(uint16_t dur=750) - : _briT(255) - , _cctT(127) - , _palT(CRGBPalette16(CRGB::Black)) + : _palT(CRGBPalette16(CRGB::Black)) , _prevPaletteBlends(0) - , _modeP(FX_MODE_STATIC) , _start(millis()) , _dur(dur) {} - Transition(uint16_t d, uint8_t b, uint8_t c, const uint32_t *o) - : _briT(b) - , _cctT(c) - , _palT(CRGBPalette16(CRGB::Black)) - , _prevPaletteBlends(0) - , _modeP(FX_MODE_STATIC) - , _start(millis()) - , _dur(d) - { - for (size_t i=0; i= width() || y >= height()) return; // if pixel would fall out of segment just exit + uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally uint16_t xX = (x+g), yY = (y+j); if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end - strip.setPixelColorXY(start + xX, startY + yY, col); + // if blending modes, blend with underlying pixel + if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); + + strip.setPixelColorXY(start + xX, startY + yY, tmpCol); if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); + if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); + else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); } if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); + if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); + else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, col); + strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); } } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e52440b0d8..8d783e05f2 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -81,9 +81,11 @@ CRGBPalette16 Segment::_randomPalette = CRGBPalette16(DEFAULT_COLOR); CRGBPalette16 Segment::_newRandomPalette = CRGBPalette16(DEFAULT_COLOR); unsigned long Segment::_lastPaletteChange = 0; // perhaps it should be per segment +bool Segment::_modeBlend = false; + // copy constructor Segment::Segment(const Segment &orig) { - //DEBUG_PRINTLN(F("-- Copy segment constructor --")); + //DEBUG_PRINTF("-- Copy segment constructor: %p -> %p\n", &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); transitional = false; // copied segment cannot be in transition name = nullptr; @@ -92,12 +94,12 @@ Segment::Segment(const Segment &orig) { _t = nullptr; if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } - //if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } + //if (orig._t) { _t = new Transition(orig._t->_dur); } } // move constructor Segment::Segment(Segment &&orig) noexcept { - //DEBUG_PRINTLN(F("-- Move segment constructor --")); + //DEBUG_PRINTF("-- Move segment constructor: %p -> %p\n", &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.transitional = false; // old segment cannot be in transition any more orig.name = nullptr; @@ -108,12 +110,15 @@ Segment::Segment(Segment &&orig) noexcept { // copy assignment Segment& Segment::operator= (const Segment &orig) { - //DEBUG_PRINTLN(F("-- Copying segment --")); + //DEBUG_PRINTF("-- Copying segment: %p -> %p\n", &orig, this); if (this != &orig) { // clean destination transitional = false; // copied segment cannot be in transition if (name) delete[] name; - if (_t) delete _t; + if (_t) { + if (_t->_segT._dataT) free(_t->_segT._dataT); + delete _t; + } deallocateData(); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); @@ -133,12 +138,16 @@ Segment& Segment::operator= (const Segment &orig) { // move assignment Segment& Segment::operator= (Segment &&orig) noexcept { - //DEBUG_PRINTLN(F("-- Moving segment --")); + //DEBUG_PRINTF("-- Moving segment: %p -> %p\n", &orig, this); if (this != &orig) { transitional = false; // just temporary if (name) { delete[] name; name = nullptr; } // free old name deallocateData(); // free old runtime data - if (_t) { delete _t; _t = nullptr; } + if (_t) { + if (_t->_segT._dataT) free(_t->_segT._dataT); + delete _t; + _t = nullptr; + } memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.transitional = false; // old segment cannot be in transition orig.name = nullptr; @@ -151,12 +160,18 @@ Segment& Segment::operator= (Segment &&orig) noexcept { bool Segment::allocateData(size_t len) { if (data && _dataLen == len) return true; //already allocated + //DEBUG_PRINTF("-- Allocating data (%d): %p\n", len, this); deallocateData(); - if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) return false; //not enough memory + if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { + // not enough memory + DEBUG_PRINTF("!!! Effect RAM depleted: %d/%d !!!\n", len, Segment::getUsedSegmentData()); + return false; + } // do not use SPI RAM on ESP32 since it is slow data = (byte*) malloc(len); - if (!data) return false; //allocation failed + if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } //allocation failed Segment::addUsedSegmentData(len); + //DEBUG_PRINTF("--- Allocated data (%p): %d/%d -> %p\n", this, len, Segment::getUsedSegmentData(), data); _dataLen = len; memset(data, 0, len); return true; @@ -164,9 +179,11 @@ bool Segment::allocateData(size_t len) { void Segment::deallocateData() { if (!data) return; + //DEBUG_PRINTF("--- Released data (%p): %d/%d -> %p\n", this, _dataLen, Segment::getUsedSegmentData(), data); free(data); data = nullptr; - Segment::addUsedSegmentData(-_dataLen); + // WARNING it looks like we have a memory leak somewhere + Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); _dataLen = 0; } @@ -179,7 +196,7 @@ void Segment::deallocateData() { */ void Segment::resetIfRequired() { if (!reset) return; - + //DEBUG_PRINTF("-- Segment reset: %p\n", this); deallocateData(); next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; @@ -215,7 +232,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(random8(), random8(160, 255), random8(128, 255))); _lastPaletteChange = millis(); - handleRandomPalette(); // do initial blend + handleRandomPalette(); // do a 1st pass of blend } targetPalette = _randomPalette; break;} @@ -270,11 +287,8 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { void Segment::startTransition(uint16_t dur) { if (!dur) { - transitional = false; - if (_t) { - delete _t; - _t = nullptr; - } + if (_t) _t->_dur = dur; // this will stop transition in next handleTransisiton() + else transitional = false; return; } if (transitional && _t) return; // already in transition no need to store anything @@ -283,44 +297,151 @@ void Segment::startTransition(uint16_t dur) { _t = new Transition(dur); // no previous transition running if (!_t) return; // failed to allocate data + //DEBUG_PRINTF("-- Started transition: %p\n", this); + swapSegenv(_t->_segT); CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette); - _t->_briT = on ? opacity : 0; - _t->_cctT = cct; - _t->_palT = _palT; - _t->_modeP = mode; - for (size_t i=0; i_colorT[i] = colors[i]; + _t->_palT = _palT; + _t->_modeT = mode; + _t->_briT = on ? opacity : 0; + _t->_cctT = cct; + _t->_segT._optionsT |= 0b0000000001000000; // mark old segment transitional + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF("-- Allocated duplicate data (%d): %p\n", _dataLen, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; + } + } transitional = true; // setOption(SEG_OPTION_TRANSITIONAL, true); } +void Segment::stopTransition() { + if (!transitional) return; + transitional = false; // finish transitioning segment + //DEBUG_PRINTF("-- Stopping transition: %p\n", this); + if (_t) { + if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { + //DEBUG_PRINTF("-- Released duplicate data (%d): %p\n", _t->_segT._dataLenT, _t->_segT._dataT); + free(_t->_segT._dataT); + _t->_segT._dataT = nullptr; + } + delete _t; + _t = nullptr; + } +} + +void Segment::handleTransition() { + if (!transitional) return; + uint16_t _progress = progress(); + if (_progress == 0xFFFFU) stopTransition(); +} + // transition progression between 0-65535 uint16_t Segment::progress() { - if (!transitional || !_t) return 0xFFFFU; - unsigned long timeNow = millis(); - if (timeNow - _t->_start > _t->_dur || _t->_dur == 0) return 0xFFFFU; - return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; + if (transitional && _t) { + unsigned long timeNow = millis(); + if (_t->_dur > 0 && timeNow - _t->_start < _t->_dur) return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; + } + return 0xFFFFU; +} + +void Segment::swapSegenv(tmpsegd_t &tmpSeg) { + if (!_t) return; + //DEBUG_PRINTF("-- Saving temp seg: %p (%p)\n", this, tmpSeg); + tmpSeg._optionsT = options; + for (size_t i=0; i_segT)) { + // swap SEGENV with transitional data + options = _t->_segT._optionsT; + for (size_t i=0; i_segT._colorT[i]; + speed = _t->_segT._speedT; + intensity = _t->_segT._intensityT; + custom1 = _t->_segT._custom1T; + custom2 = _t->_segT._custom2T; + custom3 = _t->_segT._custom3T; + check1 = _t->_segT._check1T; + check2 = _t->_segT._check2T; + check3 = _t->_segT._check3T; + aux0 = _t->_segT._aux0T; + aux1 = _t->_segT._aux1T; + step = _t->_segT._stepT; + call = _t->_segT._callT; + data = _t->_segT._dataT; + _dataLen = _t->_segT._dataLenT; + } + //DEBUG_PRINTF("-- temp seg data: %p (%d,%p)\n", this, _dataLen, data); +} + +void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { + //DEBUG_PRINTF("-- Restoring temp seg: %p (%p)\n", this, tmpSeg); + if (_t && &(_t->_segT) != &tmpSeg) { + // update possibly changed variables to keep old effect running correctly + _t->_segT._aux0T = aux0; + _t->_segT._aux1T = aux1; + _t->_segT._stepT = step; + _t->_segT._callT = call; + //if (_t->_segT._dataT != data) DEBUG_PRINTF("--- data re-allocated: (%p) %p -> %p\n", this, _t->_segT._dataT, data); + _t->_segT._dataT = data; // sometimes memory gets re-allocated (!! INVESTIGATE WHY !!) + _t->_segT._dataLenT = _dataLen; // sometimes memory gets re-allocated (!! INVESTIGATE WHY !!) + } + options = tmpSeg._optionsT; + for (size_t i=0; i_cctT * (0xFFFFU - prog)) >> 16; else return ((briNew * prog) + _t->_briT * (0xFFFFU - prog)) >> 16; - } else { - return briNew; } + return briNew; } uint8_t Segment::currentMode(uint8_t newMode) { - return (progress()>32767U) ? newMode : _t->_modeP; // change effect in the middle of transition + uint16_t prog = progress(); // implicit check for transitional & _t in progress() + if (prog < 0xFFFFU) return _t->_modeT; + return newMode; } uint32_t Segment::currentColor(uint8_t slot, uint32_t colorNew) { - return transitional && _t ? color_blend(_t->_colorT[slot], colorNew, progress(), true) : colorNew; + return transitional && _t ? color_blend(_t->_segT._colorT[slot], colorNew, progress(), true) : colorNew; } CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); - if (transitional && _t && progress() < 0xFFFFU) { + if (progress() < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms @@ -332,18 +453,6 @@ CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal return targetPalette; } -void Segment::handleTransition() { - if (!transitional) return; - uint16_t _progress = progress(); - if (_progress == 0xFFFFU) transitional = false; // finish transitioning segment - if (_t) { // thanks to @nXm AKA https://github.com/NMeirer - if (_progress == 0xFFFFU) { - delete _t; - _t = nullptr; - } - } -} - // relies on WS2812FX::service() to call it max every 8ms or more (MIN_SHOW_DELAY) void Segment::handleRandomPalette() { // just do a blend; if the palettes are identical it will just compare 48 bytes (same as _randomPalette == _newRandomPalette) @@ -426,6 +535,7 @@ void Segment::setCCT(uint16_t k) { void Segment::setOpacity(uint8_t o) { if (opacity == o) return; if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + DEBUG_PRINT(F("-- Setting opacity: ")); DEBUG_PRINTLN(o); opacity = o; stateChanged = true; // send UDP/WS broadcast } @@ -442,9 +552,9 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4)) { if (fx != mode) { + if (!transitional) { DEBUG_PRINT(F("-- Started mode transition. ")); DEBUG_PRINTLN(opacity);} if (fadeTransition) startTransition(strip.getTransition()); // set effect transitions mode = fx; - // load default values from effect string if (loadDefaults) { int16_t sOpt; @@ -625,6 +735,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } i += start; // starting pixel in a group + uint32_t tmpCol = col; // set all the pixels in the group for (int j = 0; j < grouping; j++) { uint16_t indexSet = i + ((reverse) ? -j : j); @@ -633,11 +744,13 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) uint16_t indexMir = stop - indexSet + start - 1; indexMir += offset; // offset/phase if (indexMir >= stop) indexMir -= len; // wrap - strip.setPixelColor(indexMir, col); + if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); + strip.setPixelColor(indexMir, tmpCol); } indexSet += offset; // offset/phase if (indexSet >= stop) indexSet -= len; // wrap - strip.setPixelColor(indexSet, col); + if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); + strip.setPixelColor(indexSet, tmpCol); } } } @@ -843,7 +956,7 @@ void Segment::fade_out(uint8_t rate) { const uint16_t rows = virtualHeight(); // will be 1 for 1D rate = (255-rate) >> 1; - float mappedRate = float(rate) +1.1; + float mappedRate = float(rate) +1.1f; uint32_t color = colors[1]; // SEGCOLOR(1); // target color int w2 = W(color); @@ -1079,8 +1192,10 @@ void WS2812FX::service() { // reset the segment runtime data if needed seg.resetIfRequired(); + if (!seg.isActive()) continue; + // last condition ensures all solid segments are updated at the same time - if (seg.isActive() && (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))) + if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; uint16_t delay = FRAMETIME; @@ -1095,10 +1210,23 @@ void WS2812FX::service() { if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); - // effect blending (execute previous effect) - // actual code may be a bit more involved as effects have runtime data including allocated memory - //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); - delay = (*_mode[seg.currentMode(seg.mode)])(); + // Effect blending + // When two effects are being blended, each may have different segment data, this + // data needs to be saved first and then restored before running previous/transitional mode. + // The blending will largely depend on the effect behaviour since actual output (LEDs) may be + // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer + // would need to be allocated for each effect and then blended together for each pixel. + uint8_t tmpMode = seg.currentMode(seg.mode); // this will return old mode while in transition + delay = (*_mode[seg.mode])(); // run new/current mode + if (seg.mode != tmpMode) { + Segment::tmpsegd_t _tmpSegData; + Segment::modeBlend(true); // set semaphore + seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) + uint16_t d2 = (*_mode[tmpMode])(); // run old mode + seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) + delay = MIN(delay,d2); // use shortest delay + Segment::modeBlend(false); // unset semaphore + } if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition } @@ -1273,9 +1401,7 @@ void WS2812FX::setMode(uint8_t segid, uint8_t m) { if (m >= getModeCount()) m = getModeCount() - 1; if (_segments[segid].mode != m) { - _segments[segid].startTransition(_transitionDur); // set effect transitions - _segments[segid].markForReset(); - _segments[segid].mode = m; + _segments[segid].setMode(m); // do not load defaults } } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 7ea44b158f..13b7bd82f0 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -148,6 +148,10 @@ void BusDigital::show() { else pix += _skip; PolyBus::setPixelColor(_busPtr, _iType, pix, c, co); } + #if !defined(STATUSLED) || STATUSLED>=0 + if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black + #endif + for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black } PolyBus::show(_busPtr, _iType, !_buffering); // faster if buffer consistency is not important } diff --git a/wled00/json.cpp b/wled00/json.cpp index c0c7298907..5fa4c04feb 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -29,8 +29,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) id = strip.getSegmentsNum()-1; // segments are added at the end of list } + //DEBUG_PRINTLN("-- JSON deserialize segment."); Segment& seg = strip.getSegment(id); + //DEBUG_PRINTF("-- Original segment: %p\n", &seg); Segment prev = seg; //make a backup so we can tell if something changed + //DEBUG_PRINTF("-- Duplicate segment: %p\n", &prev); uint16_t start = elem["start"] | seg.start; if (stop < 0) { diff --git a/wled00/led.cpp b/wled00/led.cpp index 97499e76d4..db49ec2a8d 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -37,12 +37,12 @@ void applyValuesToSelectedSegs() if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;} if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;} - if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette); stateChanged = true;} - if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent); stateChanged = true;} + if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);} + if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);} uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]); uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]); - if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0); stateChanged = true;} - if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1); stateChanged = true;} + if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);} + if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);} } } From 8533d7488986c78bec3f5ac77ca9663ed0d61b91 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 23 Aug 2023 10:55:46 -0700 Subject: [PATCH 203/263] Wizmote controls --- usermods/Tubes/Tubes.h | 2 +- usermods/Tubes/controller.h | 103 +++++++++++++++++++++++++++--------- usermods/Tubes/debug.h | 2 +- 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 6b43794151..8198b808f7 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -56,7 +56,7 @@ class TubesUsermod : public Usermod { globalTimer.setup(); beats.setup(); controller.setup(); - if (controller.isMaster()) { + if (controller.isMasterRole()) { master = new Master(&controller); master->setup(); } diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3339cbaaea..c402859245 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -164,7 +164,7 @@ class PatternController : public MessageReceiver { } } - bool isMaster() { + bool isMasterRole() { return role >= MasterRole; } @@ -576,7 +576,9 @@ class PatternController : public MessageReceiver { } void load_options(ControllerOptions &options) { - strip.setBrightness(options.brightness); + // Power-saving devices retain their WLED brightness + if (!power_save) + strip.setBrightness(options.brightness); } void load_pattern(TubeState &tube_state) { @@ -791,14 +793,22 @@ class PatternController : public MessageReceiver { Serial.printf("brightness: %d\n", brightness); options.brightness = brightness; - broadcast_options(); + load_options(options); + + // The master controls all followers + if (!node->is_following()) + broadcast_options(); } void setDebugging(bool debugging) { Serial.printf("debugging: %d\n", debugging); options.debugging = debugging; - broadcast_options(); + load_options(options); + + // The master controls all followers + if (!node->is_following()) + broadcast_options(); } void togglePowerSave() { @@ -1134,9 +1144,6 @@ class PatternController : public MessageReceiver { } void broadcast_options() { - if (!node->is_following()) { - load_options(options); - } node->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } @@ -1194,7 +1201,7 @@ class PatternController : public MessageReceiver { case COMMAND_BEATS: // the master control ignores this request, it has its own // beat measuring. - if (isMaster()) + if (isMasterRole()) return false; set_tapped_bpm(*(accum88*)data, 0); return true; @@ -1295,52 +1302,98 @@ class PatternController : public MessageReceiver { #define WIZMOTE_BUTTON_BRIGHT_DOWN 8 virtual bool onButton(uint8_t button_id) { - Serial.printf("Button %d\n", button_id); + bool isMaster = !this->node->is_following(); + switch (button_id) { case WIZMOTE_BUTTON_ON: WLED::instance().initAP(true); + setDebugging(true); acknowledge(); return true; case WIZMOTE_BUTTON_OFF: WiFi.softAPdisconnect(true); + WiFi.disconnect(false, true); + apActive = false; + apBehavior = AP_BEHAVIOR_BUTTON_ONLY; + setDebugging(false); acknowledge(); return true; case WIZMOTE_BUTTON_ONE: - // Make it interesting - switch to a good pattern and timesync mode - acknowledge(); + // Make it interesting - switch to a good pattern and sync mode + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote preset 1: de-sync"); + + while (next_state.pattern_sync_id == All) + set_next_pattern(0); + + this->force_next(); + return true; + + case WIZMOTE_BUTTON_TWO: + // Apply an interesting effect & sync layer + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote preset 2: add an effect"); + + while (next_state.effect_params.effect == None) + set_next_effect(0); + + this->force_next(); + return true; + + case WIZMOTE_BUTTON_THREE: + // Turn on flames + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote preset 3: flames!"); + next_state.pattern_id = 63; // Fire + next_state.pattern_sync_id = SyncMode::All; + + this->force_next(); return true; case WIZMOTE_BUTTON_FOUR: - // Turn on or off debugging - setDebugging(!options.debugging); - acknowledge(); + // Make it an interesting combo + // Only the master will respond to this + if (!isMaster) + return false; + + // 38: Noise 3 + Serial.println("WizMote preset 4: interesting pattern"); + set_next_pattern(0); + next_state.pattern_id = 38; // overwrite with: Noise 3 + + this->force_next(); return true; case WIZMOTE_BUTTON_BRIGHT_UP: + // Brighten (ignored if in power save mode) + Serial.println("WizMote: brightness up"); if (options.brightness <= 230) setBrightness(options.brightness + 25); return true; case WIZMOTE_BUTTON_BRIGHT_DOWN: + // Dim (ignored if in power save mode) + Serial.println("WizMote: brightness down"); + if (options.brightness >= 25) setBrightness(options.brightness - 25); return true; - case WIZMOTE_BUTTON_NIGHT: - // Turn off debugging and wifi - WiFi.softAPdisconnect(true); - setDebugging(false); - acknowledge(); - return true; - + // case WIZMOTE_BUTTON_NIGHT: - // case WIZMOTE_BUTTON_ON : setOn(); stateUpdated(CALL_MODE_BUTTON); break; - // case WIZMOTE_BUTTON_OFF : setOff(); stateUpdated(CALL_MODE_BUTTON); break; - // case WIZMOTE_BUTTON_TWO : presetWithFallback(2, FX_MODE_BREATH, 0); resetNightMode(); break; - // case WIZMOTE_BUTTON_THREE : presetWithFallback(3, FX_MODE_FIRE_FLICKER, 0); resetNightMode(); break; default: + Serial.printf("Button %d master=%d\n", button_id, isMaster); return false; } } diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index c6870f444c..0d2a895fda 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -77,7 +77,7 @@ class DebugController { ); Serial.printf("=== Controller: "); - if (controller->isMaster()) { + if (controller->isMasterRole()) { Serial.print("PRIMARY "); } if (controller->sound.active) { From f2ca7b0288960e6e8c69e749d6ef80b9173be05d Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 23 Aug 2023 11:28:07 -0700 Subject: [PATCH 204/263] Release version 12 --- usermods/Tubes/updater.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index 43923a19e4..cf2ccc9a66 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 11 +#define RELEASE_VERSION 12 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { From 6239c0a0b9be3a0ca1eac8121e87a82d6e2f0beb Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 24 Aug 2023 00:37:28 -0700 Subject: [PATCH 205/263] Fix the wizmote presets to work more reliably --- usermods/Tubes/controller.h | 50 +++++++++++++++++++++++++++++-------- usermods/Tubes/firmware.sh | 7 +++--- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c402859245..514d2a9f0d 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -30,8 +30,8 @@ const static uint8_t DEFAULT_TANK_BRIGHTNESS = 240; #define ROLE_EEPROM_LOCATION 2559 #define BOOT_OPTIONS_EEPROM_LOCATION 2551 -#define IDENTIFY_STUCK_PATTERNS -#define IDENTIFY_STUCK_PALETTES +// #define IDENTIFY_STUCK_PATTERNS +// #define IDENTIFY_STUCK_PALETTES typedef struct { bool debugging; @@ -1301,6 +1301,18 @@ class PatternController : public MessageReceiver { #define WIZMOTE_BUTTON_BRIGHT_UP 9 #define WIZMOTE_BUTTON_BRIGHT_DOWN 8 + void force_next_pattern() { + next_state.pattern_phrase = current_state.beat_frame >> 12; + if (next_state.palette_phrase == next_state.pattern_phrase) + next_state.palette_phrase += random8(0, 5); + force_next(); + } + + void force_next_effect() { + next_state.effect_phrase = current_state.beat_frame >> 12; + force_next(); + } + virtual bool onButton(uint8_t button_id) { bool isMaster = !this->node->is_following(); @@ -1313,8 +1325,9 @@ class PatternController : public MessageReceiver { case WIZMOTE_BUTTON_OFF: WiFi.softAPdisconnect(true); - WiFi.disconnect(false, true); apActive = false; + WiFi.disconnect(false, true); + WLED::instance().enableWatchdog(); apBehavior = AP_BEHAVIOR_BUTTON_ONLY; setDebugging(false); acknowledge(); @@ -1328,10 +1341,11 @@ class PatternController : public MessageReceiver { Serial.println("WizMote preset 1: de-sync"); + set_next_pattern(0); while (next_state.pattern_sync_id == All) set_next_pattern(0); - this->force_next(); + this->force_next_pattern(); return true; case WIZMOTE_BUTTON_TWO: @@ -1342,23 +1356,26 @@ class PatternController : public MessageReceiver { Serial.println("WizMote preset 2: add an effect"); + set_next_effect(0); while (next_state.effect_params.effect == None) set_next_effect(0); - this->force_next(); + this->force_next_effect(); return true; case WIZMOTE_BUTTON_THREE: - // Turn on flames + // Turn on flames. Also up the tempo to 125 // Only the master will respond to this if (!isMaster) return false; + // Switch to house mode + set_tapped_bpm(125<<8); + Serial.println("WizMote preset 3: flames!"); next_state.pattern_id = 63; // Fire next_state.pattern_sync_id = SyncMode::All; - - this->force_next(); + this->force_next_pattern(); return true; case WIZMOTE_BUTTON_FOUR: @@ -1369,10 +1386,11 @@ class PatternController : public MessageReceiver { // 38: Noise 3 Serial.println("WizMote preset 4: interesting pattern"); + set_next_pattern(0); next_state.pattern_id = 38; // overwrite with: Noise 3 - this->force_next(); + this->force_next_pattern(); return true; case WIZMOTE_BUTTON_BRIGHT_UP: @@ -1390,7 +1408,19 @@ class PatternController : public MessageReceiver { setBrightness(options.brightness - 25); return true; - // case WIZMOTE_BUTTON_NIGHT: + case WIZMOTE_BUTTON_NIGHT: + // Chill mode + // Only the master will respond to this + if (!isMaster) + return false; + + Serial.println("WizMote: chill"); + + // Switch to deep house mode + set_tapped_bpm(120<<8); + + this->force_next(); + return true; default: Serial.printf("Button %d master=%d\n", button_id, isMaster); diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index e92d0b4a13..4f811552aa 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -20,13 +20,14 @@ update_config() { # return; echo "Updating configuration via OTA" - curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" >/dev/null - curl -s http://$1/reset >/dev/null + curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" -H "Connection: close" --no-keepalive + echo "Configured; wait..." + curl -s http://$1/reset -H "Connection: close" >/dev/null } update_firmware() { echo "Updating firmware via OTA" - curl -s -F "update=@../../build_output/firmware/tubes.bin" $1/update >/dev/null + curl -s -F "update=@../../build_output/firmware/tubes.bin" -H "Connection: close" --no-keepalive $1/update >/dev/null echo "Updated; wait..." sleep 5 update_config $1 From 38fc5b50af9fafdf4b264e7d19f4119dce97b6ca Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 24 Aug 2023 03:59:09 -0700 Subject: [PATCH 206/263] Fix newly flashed units having no brightness --- usermods/Tubes/controller.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 514d2a9f0d..3e1481745b 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -213,7 +213,7 @@ class PatternController : public MessageReceiver { options.brightness = DEFAULT_TUBE_BRIGHTNESS; } options.debugging = false; - load_options(options); + load_options(options, true); #ifdef USELCD lcd->setup(); @@ -575,9 +575,9 @@ class PatternController : public MessageReceiver { Serial.println(); } - void load_options(ControllerOptions &options) { + void load_options(ControllerOptions &options, bool init=false) { // Power-saving devices retain their WLED brightness - if (!power_save) + if (init || !power_save) strip.setBrightness(options.brightness); } From 1b838f21757647ad9489c33b0ea2acebd93ede23 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Apr 2024 12:12:17 -0700 Subject: [PATCH 207/263] Christmas and Golden modes --- usermods/Tubes/controller.h | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 3e1481745b..24cfac996f 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -165,6 +165,9 @@ class PatternController : public MessageReceiver { } bool isMasterRole() { +#if defined(GOLDEN) || defined(CHRISTMAS) + return true; +#endif return role >= MasterRole; } @@ -212,6 +215,9 @@ class PatternController : public MessageReceiver { } else { options.brightness = DEFAULT_TUBE_BRIGHTNESS; } +#if defined(GOLDEN) || defined(CHRISTMAS) + node->reset(0xFFF); +#endif options.debugging = false; load_options(options, true); @@ -670,8 +676,22 @@ class PatternController : public MessageReceiver { // Choose the palette to display at the next palette cycle // Return the number of phrases until the next palette cycle uint16_t set_next_palette(uint16_t phrase) { +#if defined(GOLDEN) + uint r = random8(0, 4); + uint colors[4] = {18, 58, 71, 111}; + next_state.palette_id = colors[r]; +#elif defined(CHRISTMAS) // 81, 107 are too bright + uint r = random8(0, 20); + uint colors[20] = {/*gold:*/18, 58, 71, 111, + /*yes:*/25, 34, 61, 63, 81, 112, + /*best yes:*/25, 34, 34, 61, 63, 81, 112, + /*maybe:*/81, 28, 107}; + next_state.palette_id = colors[r]; +#else // Don't select the built-in palettes next_state.palette_id = random8(6, gGradientPaletteCount); +#endif + auto phrases = random8(MIN_COLOR_CHANGE_PHRASES, MAX_COLOR_CHANGE_PHRASES); // Change color more often in boring patterns @@ -948,7 +968,8 @@ class PatternController : public MessageReceiver { // If not the lead, send it to the lead. uint8_t b; accum88 arg = parse_number(command+1); - + Serial.printf("[command=%c arg=%04x]\n", command[0], arg); + switch (command[0]) { case 'd': setDebugging(!options.debugging); From 69f1bd8be708438bd4efe9711301c63604f26303 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 28 Apr 2024 18:21:22 -0700 Subject: [PATCH 208/263] Update to V13; always use WLED's own palette and pattern fading --- usermods/Tubes/controller.h | 42 ++++++++----------------------------- usermods/Tubes/firmware.sh | 14 +++++++++++++ usermods/Tubes/updater.h | 2 +- wled00/const.h | 2 +- wled00/palettes.h | 2 +- wled00/wled.h | 1 - 6 files changed, 26 insertions(+), 37 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 24cfac996f..bea5b386b0 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -430,16 +430,6 @@ class PatternController : public MessageReceiver { // In manual mode WLED is always active if (patternOverride) { wled_fader = 0xFFFF; - transition_mode_point = 0; - } else if (wled_fader == 0xFFFF) { - // When fading down... - // Wait until the transition has completely changed - // before switching to new mode - transition_mode_point = 0xFFFFU; - } else if (wled_fader == 0) { - // When fading up... - // Transition to new mode immediately - transition_mode_point = 0; } uint16_t length = strip.getLengthTotal(); @@ -772,14 +762,10 @@ class PatternController : public MessageReceiver { void set_wled_palette(uint8_t palette_id) { if (paletteOverride) palette_id = paletteOverride; - - for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { - Segment& seg = strip.getSegment(i); - if (seg.palette == palette_id) continue; - if (!seg.isActive()) continue; - seg.startTransition(strip.getTransition()); - seg.palette = palette_id; - } + + Segment& seg = strip.getMainSegment(); + seg.setPalette(palette_id); + stateChanged = true; stateUpdated(CALL_MODE_DIRECT_CHANGE); } @@ -790,21 +776,11 @@ class PatternController : public MessageReceiver { else if (pattern_id == 0) pattern_id = DEFAULT_WLED_FX; // Never set it to solid - // When fading IN, make the pattern transition immediate if possible - bool fadeIn = wled_fader < 2000; - for (uint8_t i=0; i < strip.getSegmentsNum(); i++) { - Segment& seg = strip.getSegment(i); - if (!seg.isActive()) continue; - if (seg.mode == pattern_id) continue; - if (fadeIn) { - seg.startTransition(0); - } else { - seg.startTransition(strip.getTransition()); - } - seg.speed = speed; - seg.intensity = intensity; - strip.setMode(i, pattern_id); - } + Segment& seg = strip.getMainSegment(); + seg.speed = speed; + seg.intensity = intensity; + seg.setMode(pattern_id); + stateChanged = true; stateUpdated(CALL_MODE_DIRECT_CHANGE); } diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 4f811552aa..20a8a544cb 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -3,6 +3,20 @@ # Updates new boards (which start as broadcasting on WLED-AP) to custom firmware # Will update as many boards as are plugged in, one at a time. + +# Two ways to use it: +# 1) ./firmware.sh +# This will update all boards with open APs named WLED-AP +# 2) ./firmware.sh batch +# This will update all boards with open APs named WLED-AP or WLED-UPDATE +# This is useful for updating a batch of boards at once +# To put a board in this mode, send it a V## command with a version higher than the current one +# There's also: +# 3) ./firmware.sh upload +# This will upload the firmware to the internet server, but not update any boards +# Internet upload is not working 100% yet, so this is not recommended + + WLEDPATH=../../build_output/firmware ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools diff --git a/usermods/Tubes/updater.h b/usermods/Tubes/updater.h index cf2ccc9a66..e7fee8efbc 100644 --- a/usermods/Tubes/updater.h +++ b/usermods/Tubes/updater.h @@ -6,7 +6,7 @@ #include #include "timer.h" -#define RELEASE_VERSION 12 +#define RELEASE_VERSION 13 // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { diff --git a/wled00/const.h b/wled00/const.h index b493782e90..f6549c61e3 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 116 // custom palette.h +#define GRADIENT_PALETTE_COUNT 128 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" diff --git a/wled00/palettes.h b/wled00/palettes.h index a1c982e894..8aab341c50 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -2408,7 +2408,7 @@ const byte* const gGradientPalettes[] PROGMEM = { */ }; const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); -#define GRADIENT_PALETTE_COUNT 116 +#define GRADIENT_PALETTE_COUNT 128 #endif diff --git a/wled00/wled.h b/wled00/wled.h index 8bfc043715..5834025c69 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -535,7 +535,6 @@ WLED_GLOBAL unsigned long transitionStartTime; WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s) -WLED_GLOBAL uint16_t transition_mode_point _INIT(0); // nightlight WLED_GLOBAL bool nightlightActive _INIT(false); From 9a487c65f690e8d67c8ad8b00dc777f5a25086e6 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Fri, 7 Jun 2024 17:43:21 -0700 Subject: [PATCH 209/263] fix hidden member variable so that brightness is correctly adjusted and saved --- usermods/Tubes/particle.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index 00ad72a304..9005e2ef4f 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -90,7 +90,7 @@ class Particle { CRGB color_at(uint16_t age_frac) { // Particles get dimmer with age uint8_t a = age_frac >> 8; - uint8_t brightness = scale8((uint8_t)(brightness>>8), 255-a); + brightness = scale8((uint8_t)(brightness>>8), 255-a); #ifdef PARTICLE_PALETTES // a black pattern actually means to use the current palette From f0da4d8a82d48f3c64bef9e9775478b3a3c70c41 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Sat, 8 Jun 2024 09:46:00 -0700 Subject: [PATCH 210/263] use WLED_FS macro so that appropriate LittleFS is used based on build settings --- usermods/Tubes/debug.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 0d2a895fda..3b61224ecc 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -71,8 +71,8 @@ class DebugController { knownIp[2], knownIp[3], freeMemory(), - LITTLEFS.usedBytes(), - LITTLEFS.totalBytes(), + WLED_FS.usedBytes(), + WLED_FS.totalBytes(), formatted_time(millis()).c_str() ); From 9e7d3c70650c3f96cc79f19191092a3efcb4f50a Mon Sep 17 00:00:00 2001 From: Craig Link Date: Sun, 16 Jun 2024 20:12:13 -0700 Subject: [PATCH 211/263] add support for ESP32S3 add support for ESP32-S3-Matrix update Build Environment to latest Ardunio-Espressif32 PlatformIO build 6.7.0 / 3.20017.0 update libraries include FastLED which supports Hardware SPI for ESP32 --- platformio.ini | 140 +++++++++++------------- platformio_tubes.ini | 56 ++++++++++ tools/WLED_ESP32_4MB_noOTA.csv | 6 + usermods/Tubes/Tubes.h | 13 ++- usermods/Tubes/controller.h | 2 +- usermods/audioreactive/audio_reactive.h | 2 +- 6 files changed, 136 insertions(+), 83 deletions(-) create mode 100644 platformio_tubes.ini create mode 100644 tools/WLED_ESP32_4MB_noOTA.csv diff --git a/platformio.ini b/platformio.ini index 5da7162652..391e764e85 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,8 +47,9 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_1 src_dir = ./wled00 data_dir = ./wled00/data build_cache_dir = ~/.buildcache -extra_configs = +extra_configs = platformio_override.ini + ;platformio_tubes.ini [common] # ------------------------------------------------------------------------------ @@ -142,7 +143,6 @@ build_unflags = build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags} build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} -build_flags_esp32_V4= ${common.build_flags} ${esp32_idf_V4.build_flags} ldscript_1m128k = eagle.flash.1m128.ld ldscript_2m512k = eagle.flash.2m512.ld @@ -177,9 +177,9 @@ upload_speed = 115200 # ------------------------------------------------------------------------------ lib_compat_mode = strict lib_deps = - fastled/FastLED @ 3.6.0 - IRremoteESP8266 @ 2.8.2 - makuna/NeoPixelBus @ 2.7.5 + fastled/FastLED @ 3.7.0 + IRremoteESP8266 @ 2.8.6 + makuna/NeoPixelBus @ 2.7.8 https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI @@ -221,62 +221,38 @@ build_flags = ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown lib_deps = - #https://github.com/lorol/LITTLEFS.git ESPAsyncTCP @ 1.2.2 ESPAsyncUDP ${env.lib_deps} [esp32] -#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip -platform = espressif32@3.5.0 -platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 +platform = espressif32@6.7.0 +platform_packages = framework-arduinoespressif32 @ 3.20017.0 build_flags = -g - -DARDUINO_ARCH_ESP32 - #-DCONFIG_LITTLEFS_FOR_IDF_3_2 + -D ARDUINO_ARCH_ESP32 + -D FASTLED_ALL_PINS_HARDWARE_SPI -D CONFIG_ASYNC_TCP_USE_WDT=0 - #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x - -D LOROL_LITTLEFS - ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = - https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT -AR_lib_deps = https://github.com/kosme/arduinoFFT#419d7b0 - -[esp32_idf_V4] -;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 -;; very similar to the normal ESP32 flags, but omitting Lorol LittleFS, as littlefs is included in the new framework already. -;; -;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. -;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -platform = espressif32@5.3.0 -platform_packages = -build_flags = -g - -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one - -DARDUINO_ARCH_ESP32 -DESP32 - #-DCONFIG_LITTLEFS_FOR_IDF_3_2 - -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 -default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv -lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 - ${env.lib_deps} +AR_lib_deps = https://github.com/kosme/arduinoFFT @ 2.0.2 [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = espressif32@5.3.0 -platform_packages = +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} build_flags = -g - -DARDUINO_ARCH_ESP32 - -DARDUINO_ARCH_ESP32S2 - -DCONFIG_IDF_TARGET_ESP32S2=1 + -D ARDUINO_ARCH_ESP32 + -D ARDUINO_ARCH_ESP32S2 + -D FASTLED_ALL_PINS_HARDWARE_SPI + -D CONFIG_IDF_TARGET_ESP32S2=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 - -DCO - -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! + -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_USB_DFU_ON_BOOT=0 + -D ARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT @@ -286,15 +262,15 @@ lib_deps = [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = espressif32@5.3.0 -platform_packages = +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} build_flags = -g - -DARDUINO_ARCH_ESP32 - -DARDUINO_ARCH_ESP32C3 - -DCONFIG_IDF_TARGET_ESP32C3=1 + -D ARDUINO_ARCH_ESP32 + -D ARDUINO_ARCH_ESP32C3 + -D FASTLED_ALL_PINS_HARDWARE_SPI + -D CONFIG_IDF_TARGET_ESP32C3=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DCO - -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 + -D ARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT @@ -304,16 +280,16 @@ lib_deps = [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = espressif32@5.3.0 -platform_packages = +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} build_flags = -g - -DESP32 - -DARDUINO_ARCH_ESP32 - -DARDUINO_ARCH_ESP32S3 - -DCONFIG_IDF_TARGET_ESP32S3=1 + -D ESP32 + -D ARDUINO_ARCH_ESP32 + -D ARDUINO_ARCH_ESP32S3 + -D FASTLED_ALL_PINS_HARDWARE_SPI + -D CONFIG_IDF_TARGET_ESP32S3=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 - -DCO + -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT @@ -436,9 +412,10 @@ board_build.partitions = ${esp32.default_partitions} [env:esp32_quinled_diguno] extends = env:esp32dev build_flags = ${env:esp32dev.build_flags} -D RLYPIN=12 -D BTNPIN=0 -D DATA_PINS=16 -D DMTYPE=1 -D I2S_SDPIN=19 -D I2S_WSPIN=4 -D I2S_CKPIN=18 - -D USERMOD_AUDIOREACTIVE + -D FASTLED_ESP32_SPI_BUS=HSPI + ${esp32.AR_build_flags} lib_deps = ${env:esp32dev.lib_deps} - https://github.com/blazoncek/arduinoFFT.git + ${esp32.AR_lib_deps} upload_speed = 690000 board_build.f_flash = 80000000L board_build.flash_mode = qio @@ -455,21 +432,6 @@ board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio -[env:esp32dev_V4_dio80] -;; experimental ESP32 env using ESP-IDF V4.4.x -;; Warning: this build environment is not stable!! -;; please erase your device before installing. -board = esp32dev -platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_V4_qio80 #-D WLED_DISABLE_BROWNOUT_DET -lib_deps = ${esp32_idf_V4.lib_deps} -monitor_filters = esp32_exception_decoder -board_build.partitions = ${esp32_idf_V4.default_partitions} -board_build.f_flash = 80000000L -board_build.flash_mode = dio - [env:esp32_eth] board = esp32-poe platform = ${esp32.platform} @@ -519,7 +481,7 @@ platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 ; or 460800 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB - -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ;-D WLED_DEBUG @@ -539,7 +501,7 @@ platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} - -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM @@ -838,3 +800,27 @@ lib_deps = ${esp32.lib_deps} TFT_eSPI @ ^2.3.70 board_build.partitions = ${esp32.default_partitions} + +# ------------------------------------------------------------------------------ +# ESP32 S3 Matrix M1 +# ------------------------------------------------------------------------------ +[env:esp32-s3-matrix-m1] +extends = env:esp32s3dev_8MB_PSRAM_opi +board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB +board_upload.flash_size = 4MB +board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=esp32-s3-matrix-m1 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D ARDUINO_USB_MODE=1 + ;-D WLED_USE_PSRAM + -D BOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D IRTYPE=0 + -D WLED_DISABLE_INFRARED + -D ABL_MILLIAMPS_DEFAULT=250 + -D FASTLED_ALL_PINS_HARDWARE_SPI + -D NUM_STRIPS=1 -D PIXEL_COUNTS=64 -D DEFAULT_LED_COUNT=64 + -D DEFAULT_LED_COLOR_ORDER=1 -D LEDPIN=14 +lib_deps = ${esp32s3.lib_deps} +lib_ignore = IRremoteESP8266 \ No newline at end of file diff --git a/platformio_tubes.ini b/platformio_tubes.ini new file mode 100644 index 0000000000..79e0f0f911 --- /dev/null +++ b/platformio_tubes.ini @@ -0,0 +1,56 @@ +[tubes_no_mic] +build_flags = -O2 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -D WLED_WATCHDOG_TIMEOUT=0 + -D USERMOD_TUBES + ;-D USERMOD_TUBES_DISABLE_ESPNOW + ; Disable a bunch of unnecessary integrations + -D WLED_DISABLE_BLYNK + -D WLED_DISABLE_MQTT + -D WLED_DISABLE_LOXONE + -D WLED_DISABLE_ALEXA + -D WLED_DISABLE_INFRARED + -D WLED_DISABLE_CRONIXIE + -D WLED_DISABLE_HUESYNC + -D WLED_DISABLE_WEBSOCKETS + -D WLED_DISABLE_ADALIGHT + -D WLED_DISABLE_ESPNOW + -D IRTYPE=0 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP + IRremoteESP8266 +lib_deps = + gmag11/QuickEspNow @ ^0.6.2 + gmag11/QuickDebug @ ^0.7.0 + +[tubes] +extends = tubes_no_mic +build_flags = ${tubes_no_mic.build_flags} + ${esp32.AR_build_flags} +lib_deps = ${tubes_no_mic.lib_deps} + ${esp32.AR_lib_deps} + +[env:esp32_quinled_dig2go] +extends = env:esp32_quinled_diguno +lib_ignore = + ${env:esp32_quinled_diguno.lib_ignore} +lib_deps = ${env:esp32_quinled_diguno.lib_deps} + IRremoteESP8266 @ 2.8.6 + +[env:esp32_quinled_dig2go_tubes] +extends = env:esp32_quinled_dig2go +build_unflags = + -D WLED_DISABLE_INFRARED + -D IRTYPE=0 +build_flags = + ${tubes.build_flags} + ${env:esp32_quinled_dig2go.build_flags} +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP +lib_deps = + ${tubes.lib_deps} + ${env:esp32_quinled_dig2go.lib_deps} + + diff --git a/tools/WLED_ESP32_4MB_noOTA.csv b/tools/WLED_ESP32_4MB_noOTA.csv new file mode 100644 index 0000000000..518b0b1e04 --- /dev/null +++ b/tools/WLED_ESP32_4MB_noOTA.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x2F0000, +spiffs, data, spiffs, 0x300000,0xF0000, +coredump, data, coredump,,64K \ No newline at end of file diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 8198b808f7..42aba85315 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -38,11 +38,16 @@ class TubesUsermod : public Usermod { public: void setup() { - pinMode(MASTER_PIN, INPUT_PULLUP); - pinMode(LEGACY_PIN, INPUT_PULLUP); - if (digitalRead(MASTER_PIN) == LOW) { + + if (pinManager.isPinOk(MASTER_PIN)) { + pinMode(MASTER_PIN, INPUT_PULLUP); + if(pinManager.isPinOk(LEGACY_PIN)) { + pinMode(LEGACY_PIN, INPUT_PULLUP); + } + if (digitalRead(MASTER_PIN) == LOW) { + } + isLegacy = (digitalRead(MASTER_PIN) == LOW); } - isLegacy = (digitalRead(MASTER_PIN) == LOW); randomize(); // Override some behaviors on all Tubes diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index bea5b386b0..b431c10dd8 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -199,7 +199,7 @@ class PatternController : public MessageReceiver { } if (role <= CampRole) - strip.ablMilliampsMax = 700; // Really limit for batteries + strip.ablMilliampsMax = min(ABL_MILLIAMPS_DEFAULT,700); // Really limit for batteries else if (role <= InstallationRole) strip.ablMilliampsMax = 1000; else diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index fec0525ec7..8041879a0d 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -300,7 +300,7 @@ void FFTcode(void * parameter) #endif #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT - FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant + FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #else FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #endif From a1fe05ec36fc9c8719cfccd298ebff9857744225 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 30 Jun 2024 21:49:30 -0700 Subject: [PATCH 212/263] Update readme.md Figured it was time to actually describe what this project does, vs. stock WLED readme --- readme.md | 123 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 50 deletions(-) diff --git a/readme.md b/readme.md index 57bde0fd6f..fef03b9c6a 100644 --- a/readme.md +++ b/readme.md @@ -1,65 +1,37 @@ -

- - - - - - - - +# WLED-based LED Sticks +These portable LED light poles make pretty lights for a dance party - and better yet: -

+They're **portable**! Convenient tripod bases keep them standing at attention, and a 10Ah USB battery (like the one you charge your phone with) will power them for about 8 hours. -# Welcome to my project WLED! ✨ +They're **versatile**! You can deploy them in a [wide open space at Burning Man](https://youtu.be/UtXL0ScNUoE), in a [private event space](https://youtu.be/B6OBde7zRN4), or right next to each other [in your living room](https://youtu.be/UmiRBCOAMBg). (Click into these links for videos of them in action.) -A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! +They're **coordinated**! If you put them next to each other, they sync using near-field radio. And they'll even daisy-chain, meaning the furthest ones can be really far apart as long as there are some in the middle to relay the signal. -Now with new magical sync powers! +![IMG_1521](https://github.com/SteveEisner/tubes/blob/master/assembly/poles_in_use.jpg) -## ⚙️ Features -- WS2812FX library with more than 100 special effects -- FastLED noise effects and 50 palettes -- Modern UI with color, effect and segment controls -- Segments to set different effects and colors to user defined parts of the LED string -- Settings page - configuration via the network -- Access Point and station mode - automatic failsafe AP -- Up to 10 LED outputs per instance -- Support for RGBW strips -- Up to 250 user presets to save and load colors/effects easily, supports cycling through them. -- Presets can be used to automatically execute API calls -- Nightlight function (gradually dims down) -- Full OTA software updateability (HTTP + ArduinoOTA), password protectable -- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods) -- Configurable Auto Brightness limit for safe operation -- Filesystem-based config for easier backup of presets and settings +They're **sturdy**! I built over 100 of them and have deployed them at parties in all kinds of weather, from hot & dusty windstorms to torrential rain. They've been tossed in a truck, carried around as totems, knocked or blown over, and almost all of 'em are still working. Just don't let people use them like light sabers (lesson learned.) -## 💡 Supported light control interfaces -- WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033) -- JSON and HTTP request APIs -- MQTT -- E1.31, Art-Net, DDP and TPM2.net -- [diyHue](https://github.com/diyhue/diyHue) (Wled is supported by diyHue, including Hue Sync Entertainment under udp. Thanks to [Gregory Mallios](https://github.com/gmallios)) -- [Hyperion](https://github.com/hyperion-project/hyperion.ng) -- UDP realtime -- Alexa voice control (including dimming and color) -- Sync to Philips hue lights -- Adalight (PC ambilight via serial) and TPM2 -- Sync color of multiple WLED devices (UDP notifier) -- Infrared remotes (24-key RGB, receiver required) -- Simple timers/schedules (time from NTP, timezones/DST supported) +They're also **in progress** with lots of things that could be improved. But for now: -## 📲 Quick start guide and documentation +* they play patterns from an expanding set of "genetically-driven" combinations +* they stay in sync or deliberately drift from each other in pleasing ways +* they operate internally at a certain BPM - allowing them to sync to music -See the [documentation on our official site](https://kno.wled.ge)! +## How do they work? -[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials and tools to help you get your new project up and running! +Each light tube is running custom software on an ESP32 microcontroller. The software is running a generative light program, based on the popular software WLED, that has a simple "DNA" specifying its pattern, color, effects overlay, offset, and so on. -## 🖼️ User interface - +The patterns run on a clock that's synced to a specific BPM and counts out the 4/4 rhythm of most dance music, so they morph and change on individual beats, measures, and phrases. After several beat phrases pass, a pole's DNA is mutated a little, which can cause it to change color, or adopt a new pattern, etc. -## 💾 Compatible hardware +On-board radio lets a light pole broadcast its DNA and clock timer; when others hear the signal, they can choose to follow along by updating their own pattern DNA to match it. The method of coordination is pretty simple right now: each pole has an 8-bit ID, and lower-ID poles obey higher-ID poles. -See [here](https://kno.wled.ge/basics/compatible-hardware)! +If they're all close enough, they'll soon start doing the same thing as a group and keeping their clock timers in sync. In case they're not close enough, poles also re-broadcast all the signals they receive and obey. This lets them pass along a signal through the group until all of them have found it. + +Some randomness and chaos is intentional. Radio isn't 100% reliable, so they sometimes fall out of contact and then re-connect. And in some cases, the poles will deliberately offset their own clock a bit so that they are clearly doing the same thing but not exactly at the same time. Each tube is actually running several copies of the software and smoothly "cross-fading" between them, to avoid any jarring transitions. + +## Credits + +The form factor was inspired by [Mark Lottor's Hexatron](http://www.3waylabs.com/projects/hex/). The color schemes were inspired by [the work of Christopher Schardt](https://americanart.si.edu/exhibitions/burning-man/online/christopher-schardt) and many were found on [cpt-city](http://soliton.vm.bytemark.co.uk/pub/cpt-city/). My most recent versions use the excellent [WLED](https://github.com/Aircoookie/WLED) codebase and run as a usermod. My older code was written using [FastLED](http://fastled.io/) and I still use some original patterns that are evolved versions of FastLED examples. The mesh networking is based on [Chuck Sommerville's led-swarm](https://github.com/chucks13/LED-swarm), and still follows its [theory](https://github.com/chucks13/LED-swarm/blob/master/theory.txt) for radio connection. When I kicked off this project, I used [Paul Stoffregen's WS2812Serial](https://github.com/PaulStoffregen/WS2812Serial) for smooth animation, and of course [his Teensy CPU](https://www.pjrc.com/teensy/teensyLC.html) made the whole thing possible. Standing on the shoulders of these giants let me create these in about a month, in time to debut at Burning Man 2019 and appear at many parties since! ## WLEDtubes branch changes @@ -106,6 +78,57 @@ Also, there a few changes to core library files: * New button-press code to allow a single button to handle Wi-Fi protection (it's only turned on by explicit button press) and "Power-save" for battery operation (wled00/button.cpp) * Fleet provisioning for flashing dozens of WLED controllers (wled00/wled_serial.cpp disabled to allow it) +# Original README: Welcome to WLED! ✨ + +A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! + +Now with new magical sync powers! + +## ⚙️ Features +- WS2812FX library with more than 100 special effects +- FastLED noise effects and 50 palettes +- Modern UI with color, effect and segment controls +- Segments to set different effects and colors to user defined parts of the LED string +- Settings page - configuration via the network +- Access Point and station mode - automatic failsafe AP +- Up to 10 LED outputs per instance +- Support for RGBW strips +- Up to 250 user presets to save and load colors/effects easily, supports cycling through them. +- Presets can be used to automatically execute API calls +- Nightlight function (gradually dims down) +- Full OTA software updateability (HTTP + ArduinoOTA), password protectable +- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods) +- Configurable Auto Brightness limit for safe operation +- Filesystem-based config for easier backup of presets and settings + +## 💡 Supported light control interfaces +- WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033) +- JSON and HTTP request APIs +- MQTT +- E1.31, Art-Net, DDP and TPM2.net +- [diyHue](https://github.com/diyhue/diyHue) (Wled is supported by diyHue, including Hue Sync Entertainment under udp. Thanks to [Gregory Mallios](https://github.com/gmallios)) +- [Hyperion](https://github.com/hyperion-project/hyperion.ng) +- UDP realtime +- Alexa voice control (including dimming and color) +- Sync to Philips hue lights +- Adalight (PC ambilight via serial) and TPM2 +- Sync color of multiple WLED devices (UDP notifier) +- Infrared remotes (24-key RGB, receiver required) +- Simple timers/schedules (time from NTP, timezones/DST supported) + +## 📲 Quick start guide and documentation + +See the [documentation on our official site](https://kno.wled.ge)! + +[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials and tools to help you get your new project up and running! + +## 🖼️ User interface + + +## 💾 Compatible hardware + +See [here](https://kno.wled.ge/basics/compatible-hardware)! + ## ✌️ Other Licensed under the MIT license From d5eecb1bb17d83ccb3c09b1632d833eb83adb5fa Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 30 Jun 2024 23:47:44 -0700 Subject: [PATCH 213/263] Fix build errors --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index 391e764e85..63c641b1b7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -268,6 +268,7 @@ build_flags = -g -D ARDUINO_ARCH_ESP32 -D ARDUINO_ARCH_ESP32C3 -D FASTLED_ALL_PINS_HARDWARE_SPI + -D FASTLED_ESP32_SPI_BUS=HSPI -D CONFIG_IDF_TARGET_ESP32C3=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D ARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 @@ -611,6 +612,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -D HW_PIN_CLOCKSPI=7 -D HW_PIN_DATASPI=11 -D HW_PIN_MISOSPI=9 + -D FASTLED_ESP32_SPI_BUS=FSPI ; -D STATUSLED=15 lib_deps = ${esp32s2.lib_deps} From 12b7a82eac1fa65223c3c4a11fd83b920bdb7f55 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 1 Jul 2024 08:47:59 -0700 Subject: [PATCH 214/263] Set the ESP32S2 SPI bus in the base env so all S2 based devices get the setting --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 63c641b1b7..e128616d6a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -249,6 +249,7 @@ build_flags = -g -D ARDUINO_ARCH_ESP32 -D ARDUINO_ARCH_ESP32S2 -D FASTLED_ALL_PINS_HARDWARE_SPI + -D FASTLED_ESP32_SPI_BUS=FSPI -D CONFIG_IDF_TARGET_ESP32S2=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_USB_DFU_ON_BOOT=0 @@ -612,7 +613,6 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -D HW_PIN_CLOCKSPI=7 -D HW_PIN_DATASPI=11 -D HW_PIN_MISOSPI=9 - -D FASTLED_ESP32_SPI_BUS=FSPI ; -D STATUSLED=15 lib_deps = ${esp32s2.lib_deps} From 4fa3bed66125c6e4647e16251f5d9ac37abd0710 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 4 Jul 2024 11:03:29 -0700 Subject: [PATCH 215/263] Revert unintentional changes to upstream --- .vscode/tasks.json | 42 ++++++++++++++++++++++++++++++++++++++++++ wled00/FX_2Dfcn.cpp | 2 ++ wled00/wled_serial.cpp | 1 - 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..2ee772ce16 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build: HTML and binary", + "dependsOn": [ + "Build: HTML only", + "Build: binary only" + ], + "dependsOrder": "sequence", + "problemMatcher": [ + "$platformio", + ], + }, + { + "type": "PlatformIO", + "label": "Build: binary only", + "task": "Build", + "group": { + "kind": "build", + "isDefault": true, + }, + "problemMatcher": [ + "$platformio" + ], + "presentation": { + "panel": "shared" + } + }, + { + "type": "npm", + "script": "build", + "group": "build", + "problemMatcher": [], + "label": "Build: HTML only", + "detail": "npm run build", + "presentation": { + "panel": "shared" + } + } + ] +} \ No newline at end of file diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index d96002f1af..5dc9e9ff21 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -198,8 +198,10 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) uint16_t xX = (x+g), yY = (y+j); if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end +#ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); +#endif strip.setPixelColorXY(start + xX, startY + yY, tmpCol); diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index d9fa0f4a79..4d920e3406 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -69,7 +69,6 @@ void sendBytes(){ void handleSerial() { - return; if (pinManager.isPinAllocated(hardwareRX)) return; if (!Serial) return; // arduino docs: `if (Serial)` indicates whether or not the USB CDC serial connection is open. For all non-USB CDC ports, this will always return true From e6405822a8579e26b6c236bff599ab2dd2b0f5d0 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 8 Jul 2024 09:50:26 -0700 Subject: [PATCH 216/263] fix espnow syncing --- platformio_tubes.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 79e0f0f911..70ac750b3f 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -14,7 +14,6 @@ build_flags = -O2 -D WLED_DISABLE_HUESYNC -D WLED_DISABLE_WEBSOCKETS -D WLED_DISABLE_ADALIGHT - -D WLED_DISABLE_ESPNOW -D IRTYPE=0 lib_ignore = ESPAsyncTCP @@ -46,6 +45,7 @@ build_unflags = build_flags = ${tubes.build_flags} ${env:esp32_quinled_dig2go.build_flags} + -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 lib_ignore = ESPAsyncTCP ESPAsyncUDP From 31dd6ae946c996c5424bcfa4f8d65143b4c81274 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 4 Jul 2024 11:31:07 -0700 Subject: [PATCH 217/263] Fix palette size (don't count first 13) --- wled00/const.h | 2 +- wled00/palettes.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/const.h b/wled00/const.h index f6549c61e3..b493782e90 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,7 +5,7 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 128 // custom palette.h +#define GRADIENT_PALETTE_COUNT 116 // custom palette.h //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" diff --git a/wled00/palettes.h b/wled00/palettes.h index 8aab341c50..16f26b87dd 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -2408,7 +2408,6 @@ const byte* const gGradientPalettes[] PROGMEM = { */ }; const uint8_t gGradientPaletteCount = ARRAY_SIZE(gGradientPalettes); -#define GRADIENT_PALETTE_COUNT 128 #endif From 24453b5d1d763aa9f058b68003348f2763324005 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Wed, 7 Aug 2024 08:00:31 -0700 Subject: [PATCH 218/263] add esp32-c3-athom device --- platformio_tubes.ini | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 1a0df1fc2a..1d1aa0c233 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -125,3 +125,34 @@ lib_ignore = lib_deps = ${tubes_no_mic.lib_deps} +# ------------------------------------------------------------------------------ +# ESP32 C3 Athom +# ------------------------------------------------------------------------------ +[env:esp32-c3-athom] +extends = env:esp32c3dev +lib_ignore = IRremoteESP8266 + ${env:esp32c3dev.lib_ignore} +build_flags = ${env:esp32c3dev.build_flags} + -D LEDPIN=10 + -D BTNPIN=9 + -D WLED_DISABLE_INFRARED + -D IRTYPE=0 + +[env:esp32-c3-athom_tubes] +extends = env:esp32-c3-athom +platform = ${tubes_no_mic.platform} +platform_packages = ${tubes_no_mic.platform_packages} +build_unflags = ${env:esp32-c3-athom.build_unflags} + ${tubes_no_mic.build_unflags} +build_flags = + ${tubes_no_mic.build_flags} + -D WLED_WATCHDOG_TIMEOUT=0 +; -D LOLIN_WIFI_FIX ; seems to work much better with this + -D LEDPIN=10 + -D BTNPIN=9 + -D FASTLED_ESP32_SPI_BUS=HSPI + -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 +lib_ignore = ${env:esp32-c3-athom.lib_ignore} + ${tubes_no_mic.lib_ignore} +lib_deps = + ${tubes_no_mic.lib_deps} From 8d0218d3c7ad1353a481a805cfc833d0624089cd Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 29 Jul 2024 10:49:11 -0700 Subject: [PATCH 219/263] - reverts platformio.ini to be closer to main branch ini and package versions - standarize tube builds on framework v6.7.0 / 3.20017.0 via platformio_tubes.ini --- platformio.ini | 142 +++++++++++++++++++++++-------------------- platformio_tubes.ini | 95 +++++++++++++++++++++++++---- 2 files changed, 160 insertions(+), 77 deletions(-) diff --git a/platformio.ini b/platformio.ini index e128616d6a..5da7162652 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,9 +47,8 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_1 src_dir = ./wled00 data_dir = ./wled00/data build_cache_dir = ~/.buildcache -extra_configs = +extra_configs = platformio_override.ini - ;platformio_tubes.ini [common] # ------------------------------------------------------------------------------ @@ -143,6 +142,7 @@ build_unflags = build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags} build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} +build_flags_esp32_V4= ${common.build_flags} ${esp32_idf_V4.build_flags} ldscript_1m128k = eagle.flash.1m128.ld ldscript_2m512k = eagle.flash.2m512.ld @@ -177,9 +177,9 @@ upload_speed = 115200 # ------------------------------------------------------------------------------ lib_compat_mode = strict lib_deps = - fastled/FastLED @ 3.7.0 - IRremoteESP8266 @ 2.8.6 - makuna/NeoPixelBus @ 2.7.8 + fastled/FastLED @ 3.6.0 + IRremoteESP8266 @ 2.8.2 + makuna/NeoPixelBus @ 2.7.5 https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI @@ -221,39 +221,62 @@ build_flags = ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown lib_deps = + #https://github.com/lorol/LITTLEFS.git ESPAsyncTCP @ 1.2.2 ESPAsyncUDP ${env.lib_deps} [esp32] -platform = espressif32@6.7.0 -platform_packages = framework-arduinoespressif32 @ 3.20017.0 +#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip +platform = espressif32@3.5.0 +platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 build_flags = -g - -D ARDUINO_ARCH_ESP32 - -D FASTLED_ALL_PINS_HARDWARE_SPI + -DARDUINO_ARCH_ESP32 + #-DCONFIG_LITTLEFS_FOR_IDF_3_2 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x + -D LOROL_LITTLEFS + ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = + https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT -AR_lib_deps = https://github.com/kosme/arduinoFFT @ 2.0.2 +AR_lib_deps = https://github.com/kosme/arduinoFFT#419d7b0 + +[esp32_idf_V4] +;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 +;; very similar to the normal ESP32 flags, but omitting Lorol LittleFS, as littlefs is included in the new framework already. +;; +;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. +;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. +platform = espressif32@5.3.0 +platform_packages = +build_flags = -g + -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one + -DARDUINO_ARCH_ESP32 -DESP32 + #-DCONFIG_LITTLEFS_FOR_IDF_3_2 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 +default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +lib_deps = + https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${env.lib_deps} [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} +platform = espressif32@5.3.0 +platform_packages = build_flags = -g - -D ARDUINO_ARCH_ESP32 - -D ARDUINO_ARCH_ESP32S2 - -D FASTLED_ALL_PINS_HARDWARE_SPI - -D FASTLED_ESP32_SPI_BUS=FSPI - -D CONFIG_IDF_TARGET_ESP32S2=1 + -DARDUINO_ARCH_ESP32 + -DARDUINO_ARCH_ESP32S2 + -DCONFIG_IDF_TARGET_ESP32S2=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_USB_DFU_ON_BOOT=0 - -D ARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! + -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 + -DCO + -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT @@ -263,16 +286,15 @@ lib_deps = [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} +platform = espressif32@5.3.0 +platform_packages = build_flags = -g - -D ARDUINO_ARCH_ESP32 - -D ARDUINO_ARCH_ESP32C3 - -D FASTLED_ALL_PINS_HARDWARE_SPI - -D FASTLED_ESP32_SPI_BUS=HSPI - -D CONFIG_IDF_TARGET_ESP32C3=1 + -DARDUINO_ARCH_ESP32 + -DARDUINO_ARCH_ESP32C3 + -DCONFIG_IDF_TARGET_ESP32C3=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D ARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 + -DCO + -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT @@ -282,16 +304,16 @@ lib_deps = [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} +platform = espressif32@5.3.0 +platform_packages = build_flags = -g - -D ESP32 - -D ARDUINO_ARCH_ESP32 - -D ARDUINO_ARCH_ESP32S3 - -D FASTLED_ALL_PINS_HARDWARE_SPI - -D CONFIG_IDF_TARGET_ESP32S3=1 + -DESP32 + -DARDUINO_ARCH_ESP32 + -DARDUINO_ARCH_ESP32S3 + -DCONFIG_IDF_TARGET_ESP32S3=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 + -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 + -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT @@ -414,10 +436,9 @@ board_build.partitions = ${esp32.default_partitions} [env:esp32_quinled_diguno] extends = env:esp32dev build_flags = ${env:esp32dev.build_flags} -D RLYPIN=12 -D BTNPIN=0 -D DATA_PINS=16 -D DMTYPE=1 -D I2S_SDPIN=19 -D I2S_WSPIN=4 -D I2S_CKPIN=18 - -D FASTLED_ESP32_SPI_BUS=HSPI - ${esp32.AR_build_flags} + -D USERMOD_AUDIOREACTIVE lib_deps = ${env:esp32dev.lib_deps} - ${esp32.AR_lib_deps} + https://github.com/blazoncek/arduinoFFT.git upload_speed = 690000 board_build.f_flash = 80000000L board_build.flash_mode = qio @@ -434,6 +455,21 @@ board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio +[env:esp32dev_V4_dio80] +;; experimental ESP32 env using ESP-IDF V4.4.x +;; Warning: this build environment is not stable!! +;; please erase your device before installing. +board = esp32dev +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_V4_qio80 #-D WLED_DISABLE_BROWNOUT_DET +lib_deps = ${esp32_idf_V4.lib_deps} +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32_idf_V4.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio + [env:esp32_eth] board = esp32-poe platform = ${esp32.platform} @@ -483,7 +519,7 @@ platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 ; or 460800 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB - -D WLED_WATCHDOG_TIMEOUT=0 + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ;-D WLED_DEBUG @@ -503,7 +539,7 @@ platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s3.build_flags} - -D WLED_WATCHDOG_TIMEOUT=0 + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM @@ -802,27 +838,3 @@ lib_deps = ${esp32.lib_deps} TFT_eSPI @ ^2.3.70 board_build.partitions = ${esp32.default_partitions} - -# ------------------------------------------------------------------------------ -# ESP32 S3 Matrix M1 -# ------------------------------------------------------------------------------ -[env:esp32-s3-matrix-m1] -extends = env:esp32s3dev_8MB_PSRAM_opi -board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB -board_upload.flash_size = 4MB -board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=esp32-s3-matrix-m1 - -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D WLED_WATCHDOG_TIMEOUT=0 - -D ARDUINO_USB_CDC_ON_BOOT=1 - -D ARDUINO_USB_MODE=1 - ;-D WLED_USE_PSRAM - -D BOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used - -D IRTYPE=0 - -D WLED_DISABLE_INFRARED - -D ABL_MILLIAMPS_DEFAULT=250 - -D FASTLED_ALL_PINS_HARDWARE_SPI - -D NUM_STRIPS=1 -D PIXEL_COUNTS=64 -D DEFAULT_LED_COUNT=64 - -D DEFAULT_LED_COLOR_ORDER=1 -D LEDPIN=14 -lib_deps = ${esp32s3.lib_deps} -lib_ignore = IRremoteESP8266 \ No newline at end of file diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 70ac750b3f..049d5d78be 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -1,9 +1,23 @@ [tubes_no_mic] -build_flags = -O2 +; these settings create a basic template for tubes build from +; from which all other tube envs should build +; the base build does not support +; -- mic (audio reactive) +; -- ir remotes +; For devices with those inputs use the [tubes] settings +; which adds those back in +platform = espressif32@6.7.0 +platform_packages = framework-arduinoespressif32 @ 3.20017.0 +build_unflags = + -D LOROL_LITTLEFS +build_flags = + -g + -O2 + -D FASTLED_ALL_PINS_HARDWARE_SPI + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D WLED_WATCHDOG_TIMEOUT=0 -D USERMOD_TUBES - ;-D USERMOD_TUBES_DISABLE_ESPNOW ; Disable a bunch of unnecessary integrations -D WLED_DISABLE_BLYNK -D WLED_DISABLE_MQTT @@ -14,43 +28,100 @@ build_flags = -O2 -D WLED_DISABLE_HUESYNC -D WLED_DISABLE_WEBSOCKETS -D WLED_DISABLE_ADALIGHT + -D WLED_DISABLE_ESPNOW -D IRTYPE=0 lib_ignore = ESPAsyncTCP ESPAsyncUDP IRremoteESP8266 lib_deps = + fastled/FastLED @ ^3.7.0 + makuna/NeoPixelBus @ ^2.7.8 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 gmag11/QuickEspNow @ ^0.6.2 gmag11/QuickDebug @ ^0.7.0 [tubes] extends = tubes_no_mic -build_flags = ${tubes_no_mic.build_flags} - ${esp32.AR_build_flags} +build_flags = + ${tubes_no_mic.build_flags} + ${esp32.AR_build_flags} lib_deps = ${tubes_no_mic.lib_deps} - ${esp32.AR_lib_deps} + IRremoteESP8266 @ ^2.8.6 + https://github.com/kosme/arduinoFFT @ 2.0.2 ; ${esp32.AR_LIB_deps} + [env:esp32_quinled_dig2go] +; basis quinled dig2go without any tubes support extends = env:esp32_quinled_diguno -lib_ignore = - ${env:esp32_quinled_diguno.lib_ignore} +build_unflags = ${env:esp32_quinled_diguno.build_unflags} + -D WLED_DISABLE_INFRARED + -D IRTYPE +lib_ignore = ${env:esp32_quinled_diguno.lib_ignore} lib_deps = ${env:esp32_quinled_diguno.lib_deps} IRremoteESP8266 @ 2.8.6 [env:esp32_quinled_dig2go_tubes] extends = env:esp32_quinled_dig2go +platform = ${tubes.platform} +platform_packages = ${tubes.platform_packages} build_unflags = - -D WLED_DISABLE_INFRARED - -D IRTYPE=0 + ${tubes.build_unflags} + ${env:esp32_quinled_dig2go} build_flags = ${tubes.build_flags} ${env:esp32_quinled_dig2go.build_flags} + -D FASTLED_ESP32_SPI_BUS=HSPI -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 lib_ignore = - ESPAsyncTCP - ESPAsyncUDP + ${tubes.lib_ignore} + ${env:esp32_quinled_dig2go.lib_ignore} lib_deps = ${tubes.lib_deps} - ${env:esp32_quinled_dig2go.lib_deps} + IRremoteESP8266 @ 2.8.6 + +# ------------------------------------------------------------------------------ +# ESP32 S3 Matrix M1 +# ------------------------------------------------------------------------------ +[env:esp32-s3-matrix-m1] +; builds using the default WLED settings +extends = env:esp32s3dev_8MB_PSRAM_opi +board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB +board_upload.flash_size = 4MB +board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +build_unflags = ${env:esp32s3dev_8MB_PSRAM_opi.build_unflags} + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D ARDUINO_USB_MSC_ON_BOOT=0 + -D ARDUINO_DFU_ON_BOOT=0 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=esp32-s3-matrix-m1 + -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D ARDUINO_USB_MODE=1 + -D BOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D ABL_MILLIAMPS_DEFAULT=250 + -D NUM_STRIPS=1 -D PIXEL_COUNTS=64 -D DEFAULT_LED_COUNT=64 + -D DEFAULT_LED_COLOR_ORDER=1 -D LEDPIN=14 +lib_deps = ${esp32s3.lib_deps} +;lib_ignore = IRremoteESP8266 + +[env:esp32-s3-matrix-m1_tubes] +extends = env:esp32-s3-matrix-m1 +platform = ${tubes.platform} +platform_packages = ${tubes.platform_packages} +board_build.partitions = tools/WLED_ESP32_4MB_noOTA.csv +build_unflags = ${env:esp32-s3-matrix-m1.build_unflags} + ${tubes_no_mic.build_unflags} + -D CONFIG_ASYNC_TCP_USE_WDT=0 +build_flags = + ${tubes_no_mic.build_flags} + ${env:esp32-s3-matrix-m1.build_flags} + -D IRTYPE=0 + -D FASTLED_ALL_PINS_HARDWARE_SPI +lib_ignore = + ${tubes_no_mic.lib_ignore} + ${env:esp32-s3-matrix-m1.lib_ignore} +lib_deps = + ${tubes_no_mic.lib_deps} + From 8421a04d4915b684be917ff2b35a4993b578fc07 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 8 Jul 2024 10:00:26 -0700 Subject: [PATCH 220/263] Revert "revert change to later espressif platform and FastLED 3.7" This reverts commit e2964a496f000b042f9a0a3f895bf97e57a6847a. --- platformio.ini | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/platformio.ini b/platformio.ini index 990b35e8aa..e128616d6a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -177,7 +177,7 @@ upload_speed = 115200 # ------------------------------------------------------------------------------ lib_compat_mode = strict lib_deps = - fastled/FastLED @ 3.6.0 + fastled/FastLED @ 3.7.0 IRremoteESP8266 @ 2.8.6 makuna/NeoPixelBus @ 2.7.8 https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 @@ -226,19 +226,15 @@ lib_deps = ${env.lib_deps} [esp32] -#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip -platform = espressif32@3.5.0 -platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 +platform = espressif32@6.7.0 +platform_packages = framework-arduinoespressif32 @ 3.20017.0 build_flags = -g -D ARDUINO_ARCH_ESP32 -D FASTLED_ALL_PINS_HARDWARE_SPI -D CONFIG_ASYNC_TCP_USE_WDT=0 - #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x - -D LOROL_LITTLEFS - ; -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = - https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} # additional build flags for audioreactive @@ -247,8 +243,8 @@ AR_lib_deps = https://github.com/kosme/arduinoFFT @ 2.0.2 [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = espressif32@5.3.0 -platform_packages = +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} build_flags = -g -D ARDUINO_ARCH_ESP32 -D ARDUINO_ARCH_ESP32S2 @@ -267,8 +263,8 @@ lib_deps = [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = espressif32@5.3.0 -platform_packages = +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} build_flags = -g -D ARDUINO_ARCH_ESP32 -D ARDUINO_ARCH_ESP32C3 @@ -286,8 +282,8 @@ lib_deps = [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = espressif32@5.3.0 -platform_packages = +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} build_flags = -g -D ESP32 -D ARDUINO_ARCH_ESP32 From 8cba07c91141701f55bc9f6a4ee9736defdcf48f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Thu, 4 Jul 2024 14:31:19 -0700 Subject: [PATCH 221/263] revert change to later espressif platform and FastLED 3.7 --- platformio.ini | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index e128616d6a..990b35e8aa 100644 --- a/platformio.ini +++ b/platformio.ini @@ -177,7 +177,7 @@ upload_speed = 115200 # ------------------------------------------------------------------------------ lib_compat_mode = strict lib_deps = - fastled/FastLED @ 3.7.0 + fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.6 makuna/NeoPixelBus @ 2.7.8 https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 @@ -226,15 +226,19 @@ lib_deps = ${env.lib_deps} [esp32] -platform = espressif32@6.7.0 -platform_packages = framework-arduinoespressif32 @ 3.20017.0 +#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip +platform = espressif32@3.5.0 +platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 build_flags = -g -D ARDUINO_ARCH_ESP32 -D FASTLED_ALL_PINS_HARDWARE_SPI -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x + -D LOROL_LITTLEFS + ; -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = + https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} # additional build flags for audioreactive @@ -243,8 +247,8 @@ AR_lib_deps = https://github.com/kosme/arduinoFFT @ 2.0.2 [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} +platform = espressif32@5.3.0 +platform_packages = build_flags = -g -D ARDUINO_ARCH_ESP32 -D ARDUINO_ARCH_ESP32S2 @@ -263,8 +267,8 @@ lib_deps = [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} +platform = espressif32@5.3.0 +platform_packages = build_flags = -g -D ARDUINO_ARCH_ESP32 -D ARDUINO_ARCH_ESP32C3 @@ -282,8 +286,8 @@ lib_deps = [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} +platform = espressif32@5.3.0 +platform_packages = build_flags = -g -D ESP32 -D ARDUINO_ARCH_ESP32 From 9b55c6f0cd120b38062e74b9fb9a57f68a21da34 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 29 Jul 2024 12:29:05 -0700 Subject: [PATCH 222/263] Simple Cleanup - fix function naming convention in node.h - encapuslate some of the callback functions within the LightNode class - defined packing to ensure standard packets on the network - move status_code() function to LightNode class --- usermods/Tubes/controller.h | 12 ++-- usermods/Tubes/debug.h | 15 +---- usermods/Tubes/node.h | 126 ++++++++++++++++++++---------------- 3 files changed, 79 insertions(+), 74 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b431c10dd8..d4d7338a02 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -406,7 +406,7 @@ class PatternController : public MessageReceiver { // Update current status if (updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!node->is_following() || random(0, 4) == 0) { + if (!node->isFollowing() || random(0, 4) == 0) { send_update(); } } @@ -524,7 +524,7 @@ class PatternController : public MessageReceiver { if (new_bpm == 0) new_bpm = current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; - if (node->is_following()) { + if (node->isFollowing()) { // Send a request up to ROOT broadcast_bpm(new_bpm); } else { @@ -792,7 +792,7 @@ class PatternController : public MessageReceiver { load_options(options); // The master controls all followers - if (!node->is_following()) + if (!node->isFollowing()) broadcast_options(); } @@ -803,7 +803,7 @@ class PatternController : public MessageReceiver { load_options(options); // The master controls all followers - if (!node->is_following()) + if (!node->isFollowing()) broadcast_options(); } @@ -1126,7 +1126,7 @@ class PatternController : public MessageReceiver { } void broadcast_action(Action& action) { - if (!node->is_following()) { + if (!node->isFollowing()) { onAction(&action); } node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); @@ -1311,7 +1311,7 @@ class PatternController : public MessageReceiver { } virtual bool onButton(uint8_t button_id) { - bool isMaster = !this->node->is_following(); + bool isMaster = !this->node->isFollowing(); switch (button_id) { case WIZMOTE_BUTTON_ON: diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 3b61224ecc..ef7fbbdd62 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -42,19 +42,6 @@ class DebugController { lastFrame = (uint32_t)-1; } - std::string status_code(NodeStatus status) { - switch (status) { - case NODE_STATUS_QUIET: - return std::string(" (quiet)"); - case NODE_STATUS_STARTING: - return std::string(" (starting)"); - case NODE_STATUS_STARTED: - return std::string(""); - default: - return std::string("??"); - } - } - void update() { EVERY_N_MILLISECONDS( 10000 ) { @@ -63,7 +50,7 @@ class DebugController { auto knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); Serial.printf("\n=== %s%s WiFi[ch%d] %s IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n", controller->node->node_name, - status_code(controller->node->status).c_str(), + controller->node->status_code(), WiFi.channel(), knownSsid.c_str(), knownIp[0], diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index fcb3717be7..14c9a1708f 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -25,6 +25,10 @@ #define REBROADCAST_TIME 30000 // Time at which followers are presumed re-uplinked #define WIFI_CHECK_RATE 2000 // Time at which we should check wifi status again +#pragma pack(push,4) // set packing for consist transport across network +// ideally this would have been pack 1, so we're actually wasting a +// number of bytes across the network, but we've already shipped... + typedef uint16_t MeshId; typedef struct { @@ -49,13 +53,13 @@ typedef struct { byte data[MESSAGE_DATA_SIZE] = {0}; } NodeMessage; +#pragma pack(pop) + typedef struct { uint8_t status; char message[40]; } NodeInfo; -void onDataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); - const char *command_name(CommandId command) { switch (command) { case COMMAND_STATE: @@ -85,21 +89,33 @@ class MessageReceiver { } }; -typedef enum{ - NODE_STATUS_QUIET=0, - NODE_STATUS_STARTING=1, - NODE_STATUS_STARTED=2, -} NodeStatus; - - class LightNode { public: static LightNode* instance; MessageReceiver *receiver; MeshNodeHeader header; + + typedef enum{ + NODE_STATUS_QUIET=0, + NODE_STATUS_STARTING=1, + NODE_STATUS_STARTED=2, + } NodeStatus; NodeStatus status = NODE_STATUS_QUIET; + PGM_P status_code() { + switch (status) { + case NODE_STATUS_QUIET: + return PSTR(" (quiet)"); + case NODE_STATUS_STARTING: + return PSTR(" (starting)"); + case NODE_STATUS_STARTED: + return PSTR(""); + default: + return PSTR("??"); + } + } + char node_name[20]; Timer statusTimer; // Use this timer to initialize and check wifi status @@ -138,10 +154,10 @@ class LightNode { header.id, header.uplinkId ); - configure_ap(); + configuredAP(); } - void configure_ap() { + void configuredAP() { #ifdef DEFAULT_WIFI strcpy(clientSSID, DEFAULT_WIFI); strcpy(clientPass, DEFAULT_WIFI_PASSWORD); @@ -195,7 +211,7 @@ class LightNode { } } - void print_message(NodeMessage* message, signed int rssi) { + void printMessage(NodeMessage* message, signed int rssi) { Serial.printf("%03X/%03X %s", message->header.id, message->header.uplinkId, @@ -217,7 +233,7 @@ class LightNode { if (message->header.version != header.version) { #ifdef NODE_DEBUGGING Serial.print(" -- !version "); - print_message(message, rssi); + printMessage(message, rssi); Serial.println(); #endif return; @@ -251,16 +267,16 @@ class LightNode { if (ignore) { #ifdef NODE_DEBUGGING Serial.print(" -- ignored "); - print_message(message, rssi); + printMessage(message, rssi); Serial.println(); #endif return; } // Execute the received command - if (message->recipients != RECIPIENTS_ROOT || !is_following()) { + if (message->recipients != RECIPIENTS_ROOT || !isFollowing()) { Serial.print(" >> "); - print_message(message, rssi); + printMessage(message, rssi); Serial.print(" "); // Adjust the timebase to match uplink @@ -284,7 +300,7 @@ class LightNode { // Re-broadcast the message if appropriate if (!rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { message->header = header; - if (!is_following()) + if (!isFollowing()) message->recipients = RECIPIENTS_ALL; broadcastMessage(message, true); } @@ -298,7 +314,7 @@ class LightNode { #ifdef NODE_DEBUGGING Serial.print(" <<< "); - print_message(message, 0); + printMessage(message, 0); Serial.println(); #endif @@ -323,7 +339,7 @@ class LightNode { message.recipients = RECIPIENTS_INFO; } else if (command == COMMAND_STATE) { message.recipients = RECIPIENTS_ALL; - } else if (is_following()) { + } else if (isFollowing()) { // Follower nodes must request that the root re-sends this message message.recipients = RECIPIENTS_ROOT; } else { @@ -348,7 +364,7 @@ class LightNode { void update() { // Check the last time we heard from the uplink node - if (is_following() && uplinkTimer.ended()) { + if (isFollowing() && uplinkTimer.ended()) { follow(NULL); } @@ -367,8 +383,9 @@ class LightNode { } void reset(MeshId id = 0) { - if (id == 0) + if (id == 0) { id = random(256, 4000); // Leave room at bottom and top of 12 bits + } header.id = id; follow(NULL); } @@ -398,42 +415,43 @@ class LightNode { onMeshChange(); } - bool is_following() { + bool isFollowing() { return header.uplinkId != 0; } -}; -typedef struct wizmote_message { - uint8_t program; // 0x91 for ON button, 0x81 for all others - uint8_t seq[4]; // Incremetal sequence number 32 bit unsigned integer LSB first - uint8_t byte5 = 32; // Unknown - uint8_t button; // Identifies which button is being pressed - uint8_t byte8 = 1; // Unknown, but always 0x01 - uint8_t byte9 = 100; // Unnkown, but always 0x64 - - uint8_t byte10; // Unknown, maybe checksum - uint8_t byte11; // Unknown, maybe checksum - uint8_t byte12; // Unknown, maybe checksum - uint8_t byte13; // Unknown, maybe checksum -} wizmote_message; - -void onWizmote(uint8_t* address, wizmote_message* data, uint8_t len) { - // First make sure this is a WizMote message. - if (len != sizeof(wizmote_message) || data->byte8 != 1 || data->byte9 != 100 || data->byte5 != 32) - return; - - static uint32_t last_seq = 0; - uint32_t cur_seq = data->seq[0] | (data->seq[1] << 8) | (data->seq[2] << 16) | (data->seq[3] << 24); - if (cur_seq == last_seq) - return; - last_seq = cur_seq; - - LightNode::instance->receiver->onButton(data->button); -} + typedef struct wizmote_message { + uint8_t program; // 0x91 for ON button, 0x81 for all others + uint8_t seq[4]; // Incremetal sequence number 32 bit unsigned integer LSB first + uint8_t byte5 = 32; // Unknown + uint8_t button; // Identifies which button is being pressed + uint8_t byte8 = 1; // Unknown, but always 0x01 + uint8_t byte9 = 100; // Unnkown, but always 0x64 + + uint8_t byte10; // Unknown, maybe checksum + uint8_t byte11; // Unknown, maybe checksum + uint8_t byte12; // Unknown, maybe checksum + uint8_t byte13; // Unknown, maybe checksum + } wizmote_message; + + static void onWizmote(uint8_t* address, wizmote_message* data, uint8_t len) { + // First make sure this is a WizMote message. + if (len != sizeof(wizmote_message) || data->byte8 != 1 || data->byte9 != 100 || data->byte5 != 32) + return; + + static uint32_t last_seq = 0; + uint32_t cur_seq = data->seq[0] | (data->seq[1] << 8) | (data->seq[2] << 16) | (data->seq[3] << 24); + if (cur_seq == last_seq) + return; + last_seq = cur_seq; + + instance->receiver->onButton(data->button); + } + + static void onDataReceived(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + instance->onPeerData(address, data, len, rssi, broadcast); + onWizmote(address, (wizmote_message*)data, len); + } +}; LightNode* LightNode::instance = nullptr; -void onDataReceived(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - LightNode::instance->onPeerData(address, data, len, rssi, broadcast); - onWizmote(address, (wizmote_message*)data, len); -} From cfce2e2d0950d04af5aee8a9180a7001d5d71ffb Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 29 Jul 2024 12:07:53 -0700 Subject: [PATCH 223/263] fix a few dependencies between variations --- platformio_tubes.ini | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 049d5d78be..d57940a1fc 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -10,14 +10,15 @@ platform = espressif32@6.7.0 platform_packages = framework-arduinoespressif32 @ 3.20017.0 build_unflags = -D LOROL_LITTLEFS + -D CONFIG_ASYNC_TCP_USE_WDT build_flags = -g -O2 -D FASTLED_ALL_PINS_HARDWARE_SPI -D ARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -D WLED_WATCHDOG_TIMEOUT=0 -D USERMOD_TUBES + -D CONFIG_ASYNC_TCP_RUNNING_CORE=-1 ; Disable a bunch of unnecessary integrations -D WLED_DISABLE_BLYNK -D WLED_DISABLE_MQTT @@ -74,11 +75,11 @@ build_flags = -D FASTLED_ESP32_SPI_BUS=HSPI -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 lib_ignore = - ${tubes.lib_ignore} + ESPAsyncTCP + ESPAsyncUDP ${env:esp32_quinled_dig2go.lib_ignore} lib_deps = ${tubes.lib_deps} - IRremoteESP8266 @ 2.8.6 @@ -104,7 +105,8 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= -D NUM_STRIPS=1 -D PIXEL_COUNTS=64 -D DEFAULT_LED_COUNT=64 -D DEFAULT_LED_COLOR_ORDER=1 -D LEDPIN=14 lib_deps = ${esp32s3.lib_deps} -;lib_ignore = IRremoteESP8266 +lib_ignore = ${env:esp32s3dev_8MB_PSRAM_opi.lib_ignore} + IRremoteESP8266 [env:esp32-s3-matrix-m1_tubes] extends = env:esp32-s3-matrix-m1 @@ -113,7 +115,6 @@ platform_packages = ${tubes.platform_packages} board_build.partitions = tools/WLED_ESP32_4MB_noOTA.csv build_unflags = ${env:esp32-s3-matrix-m1.build_unflags} ${tubes_no_mic.build_unflags} - -D CONFIG_ASYNC_TCP_USE_WDT=0 build_flags = ${tubes_no_mic.build_flags} ${env:esp32-s3-matrix-m1.build_flags} From 3956dfa4feb145cc1cdaf2e31261c63855cb8877 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 29 Jul 2024 12:32:53 -0700 Subject: [PATCH 224/263] add class protections to LightNode --- usermods/Tubes/node.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 14c9a1708f..45983bfa1d 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -126,6 +126,7 @@ class LightNode { LightNode::instance = this; } +protected: void onWifiConnect() { if (status == NODE_STATUS_QUIET) return; @@ -326,6 +327,8 @@ class LightNode { Serial.printf(" *** Broadcast error %d\n", err); } +public: + void sendCommand(CommandId command, void *data, uint8_t len) { if (len > MESSAGE_DATA_SIZE) { Serial.printf("Message is too big: %d vs %d\n", @@ -419,6 +422,7 @@ class LightNode { return header.uplinkId != 0; } +protected: typedef struct wizmote_message { uint8_t program; // 0x91 for ON button, 0x81 for all others uint8_t seq[4]; // Incremetal sequence number 32 bit unsigned integer LSB first From 446fa23209845e1e6244ac1cebe0b37bae3bb391 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Fri, 9 Aug 2024 09:07:22 -0700 Subject: [PATCH 225/263] fix some silly bugs during port. - statusTimer should just end verses being reset on "every" - only update global timer once - bad code: len < sizeof(WLED_MAX_DATA_LEN) - remove sizeof() - set WiFi power in ESPNOWBroadcast by new macro --- platformio_tubes.ini | 2 +- usermods/Tubes/Tubes.h | 2 ++ usermods/Tubes/beats.h | 4 ---- usermods/Tubes/node.h | 22 ++++++++++++---------- wled00/espnow_broadcast.cpp | 37 ++++++++++++++++++++++++++++--------- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 1d1aa0c233..d4363790de 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -147,7 +147,7 @@ build_unflags = ${env:esp32-c3-athom.build_unflags} build_flags = ${tubes_no_mic.build_flags} -D WLED_WATCHDOG_TIMEOUT=0 -; -D LOLIN_WIFI_FIX ; seems to work much better with this + ;-D LOLIN_WIFI_FIX ; seems to work much better with this -D LEDPIN=10 -D BTNPIN=9 -D FASTLED_ESP32_SPI_BUS=HSPI diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 42aba85315..1483347016 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -74,6 +74,8 @@ class TubesUsermod : public Usermod { randomize(); } + globalTimer.update(); + if (master) master->update(); beats.update(); diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h index c60e6b1b5e..d540ef46b5 100644 --- a/usermods/Tubes/beats.h +++ b/usermods/Tubes/beats.h @@ -16,16 +16,12 @@ class BeatController { void setup() { - globalTimer.setup(); - // Starts in phrase 1 sync(DEFAULT_BPM << 8, 0); } void update() { - globalTimer.update(); - // Maintains an accumulator with 14 bits of precision accum += globalTimer.delta_micros << 8; while (accum > micros_per_frac) { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 33cb71ab09..7ad6a72d38 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -96,7 +96,7 @@ class LightNode { case NODE_STATUS_RECEIVING: return PSTR(" (receiving)"); case NODE_STATUS_STARTED: - return PSTR(""); + return PSTR(" (started)"); default: return PSTR("??"); } @@ -110,9 +110,9 @@ class LightNode { protected: - const uint32_t STATUS_CHECK_RATE = 200; // Time at which we should check wifi status again - const uint32_t UPLINK_TIMEOUT = 20000; // Time at which uplink is presumed lost - const uint32_t REBROADCAST_TIME = 30000; // Time at which followers are presumed re-uplinked + const uint32_t STATUS_TIMEOUT_BASE = 3000; // Base time to wait to send broadcasts + const uint32_t UPLINK_TIMEOUT = 20000; // Time at which uplink is presumed lost + const uint32_t REBROADCAST_TIME = 30000; // Time at which followers are presumed re-uplinked Timer statusTimer; // Use this timer to initialize and check wifi status Timer uplinkTimer; // When this timer ends, assume uplink is lost. @@ -276,9 +276,12 @@ class LightNode { void broadcastMessage(NodeMessage *message, bool is_rebroadcast=false) { // Don't broadcast anything if this node isn't active. +#ifdef NODE_DEBUGGING + Serial.printf("broadcastMessage() - %s %s\n", status_code(), statusTimer.ended() ? "True" : "False"); +#endif if (status != NODE_STATUS_STARTED) { - if (status == NODE_STATUS_RECEIVING && statusTimer.every(STATUS_CHECK_RATE)) { + if (status == NODE_STATUS_RECEIVING && statusTimer.ended()) { status = NODE_STATUS_STARTED; statusTimer.stop(); Serial.printf("LightNode %s\n", status_code()); @@ -295,11 +298,10 @@ class LightNode { Serial.println(); #endif - __attribute__((unused)) auto err = espnowBroadcast.send((const uint8_t*)message, sizeof(*message)); - + __attribute__((unused)) auto success = espnowBroadcast.send((const uint8_t*)message, sizeof(*message)); #ifdef NODE_DEBUGGING - if (err != ESP_OK) { - Serial.printf("espnowBroadcast.send() failed: %d\n", err); + if (!success) { + Serial.println("espnowBroadcast.send() failed!"); } else { Serial.println("successful broadcast"); } @@ -429,7 +431,7 @@ class LightNode { if (NODE_STATUS_QUIET == status) { Serial.printf("checkESPNowState() - %d node_status:%s\n", state, status_code()); status = NODE_STATUS_RECEIVING; - statusTimer.start(3000 - header.id / 2); + statusTimer.start(STATUS_TIMEOUT_BASE - header.id / 2); Serial.printf("LightNode %s\n", status_code()); } break; diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 860370b374..1a27296966 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -32,11 +32,15 @@ ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); #define WIFI_EVENT_AP_START SYSTEM_EVENT_AP_START #endif -//#define ESPNOW_DEBUGGING +// #define ESPNOW_DEBUGGING #define BROADCAST_ADDR_ARRAY_INITIALIZER {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define WLED_ESPNOW_WIFI_CHANNEL 1 +#ifndef WLED_WIFI_POWER_SETTING +#define WLED_WIFI_POWER_SETTING WIFI_POWER_15dBm +#endif + typedef struct { uint8_t mac[6]; uint8_t len; @@ -206,9 +210,15 @@ void ESPNOWBroadcast::loop(size_t maxMessagesToProcess /*= 1*/) { bool ESPNOWBroadcast::send(const uint8_t* msg, size_t len) { #ifdef ESP32 static const uint8_t broadcast[] = BROADCAST_ADDR_ARRAY_INITIALIZER; - return ESP_OK == esp_now_send(broadcast, msg, len); + auto err = esp_now_send(broadcast, msg, len); +#ifdef ESPNOW_DEBUGGING + if (ESP_OK != err) { + Serial.printf( "esp_now_send() failed %d\n", err); + } +#endif + return ESP_OK == err; #else - return false; + return false; #endif } @@ -250,6 +260,10 @@ void ESPNOWBroadcastImpl::start() { auto status = WiFi.status(); if ( status >= WL_DISCONNECTED ) { if (esp_wifi_start() == ESP_OK) { + if (!WiFi.setTxPower(WLED_WIFI_POWER_SETTING)) { + auto power = WiFi.getTxPower(); + Serial.printf("setTxPower(%d) failed. getTX: %d\n", WLED_WIFI_POWER_SETTING, power); + } if (esp_now_init() == ESP_OK) { if (esp_now_register_recv_cb(ESPNOWBroadcastImpl::onESPNowRxCallback) == ESP_OK) { static esp_now_peer_info_t peer = { @@ -264,6 +278,9 @@ void ESPNOWBroadcastImpl::start() { if (esp_now_add_peer(&peer) == ESP_OK) { ESPNOWBroadcast::STATE starting {ESPNOWBroadcast::STARTING}; if (_state.compare_exchange_strong(starting, ESPNOWBroadcast::STARTED)) { +#ifdef ESPNOW_DEBUGGING + Serial.println("ESPNOWBroadcast started :)"); +#endif return; } else { #ifdef ESPNOW_DEBUGGING @@ -389,14 +406,16 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * // logMACAddr(mac); // Serial.printf( " %d:bytes rssi:%d\n", len, rssi); - if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { - if (len > sizeof(WLED_ESPNOW_MAX_MESSAGE_LENGTH)) { + if (len > WLED_ESPNOW_MAX_MESSAGE_LENGTH) { #ifdef ESPNOW_DEBUGGING - Serial.printf("Receive to large of packet %d bytes. ignoring...\n", len); + Serial.printf("Receive to large of packet %d > %d bytes. ignoring...\n", len, WLED_ESPNOW_MAX_MESSAGE_LENGTH); +#endif + } else if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { + Serial.println("Failed to aquire ring buffer. Dropping network message"); + } else { +#ifdef ESPNOW_DEBUGGING + Serial.printf("Receive %d bytes. RSSI %d\n", len, rssi); #endif - } else { - Serial.println("Failed to aquire ring buffer. Dropping network message"); - } } } From 53bb7e8d3b6c28f024a5ee4fcc502260071f172b Mon Sep 17 00:00:00 2001 From: Craig Link Date: Fri, 9 Aug 2024 09:10:18 -0700 Subject: [PATCH 226/263] remove unused build flag --- platformio_tubes.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index d4363790de..75d8d20f6b 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -147,7 +147,6 @@ build_unflags = ${env:esp32-c3-athom.build_unflags} build_flags = ${tubes_no_mic.build_flags} -D WLED_WATCHDOG_TIMEOUT=0 - ;-D LOLIN_WIFI_FIX ; seems to work much better with this -D LEDPIN=10 -D BTNPIN=9 -D FASTLED_ESP32_SPI_BUS=HSPI From ee20e07f2273f78433ce308940c72a6711f0397f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Wed, 31 Jul 2024 00:09:06 -0700 Subject: [PATCH 227/263] actually use PSRAM --- platformio_tubes.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index d57940a1fc..2aa3886f1f 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -101,6 +101,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D BOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D WLED_USE_PSRAM -D ABL_MILLIAMPS_DEFAULT=250 -D NUM_STRIPS=1 -D PIXEL_COUNTS=64 -D DEFAULT_LED_COUNT=64 -D DEFAULT_LED_COLOR_ORDER=1 -D LEDPIN=14 From fe4807596c922c2a51c2c9c8ee596df1bb3493c9 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Fri, 9 Aug 2024 09:21:32 -0700 Subject: [PATCH 228/263] clean up RX callback --- wled00/espnow_broadcast.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 1a27296966..85dadef747 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -402,19 +402,14 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * // be safe about accessing memory that isn't directly exposed to the callback rssi = 0; } - // Serial.printf( "RX from " ); - // logMACAddr(mac); - // Serial.printf( " %d:bytes rssi:%d\n", len, rssi); - if (len > WLED_ESPNOW_MAX_MESSAGE_LENGTH) { -#ifdef ESPNOW_DEBUGGING - Serial.printf("Receive to large of packet %d > %d bytes. ignoring...\n", len, WLED_ESPNOW_MAX_MESSAGE_LENGTH); -#endif - } else if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { - Serial.println("Failed to aquire ring buffer. Dropping network message"); + if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { + Serial.printf("Failed to queue message (%d bytes) to ring buffer. Dropping message\n", len); } else { #ifdef ESPNOW_DEBUGGING - Serial.printf("Receive %d bytes. RSSI %d\n", len, rssi); + Serial.printf("Received %d bytes from "); + logMACAddr(mac); + Serial.printf(" RSSI %d\n", len, rssi); #endif } } From a522febed4530c30b978f9499c92b3cb55a4014e Mon Sep 17 00:00:00 2001 From: Craig Link Date: Fri, 2 Aug 2024 16:54:38 -0700 Subject: [PATCH 229/263] add RSSI support back for modern IDF builds --- usermods/Tubes/node.h | 4 +-- wled00/espnow_broadcast.cpp | 53 ++++++++++++++++++++++++++++++++++--- wled00/espnow_broadcast.h | 2 +- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 5260b3a2a6..33cb71ab09 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -467,10 +467,10 @@ class LightNode { receiver->onButton(data->button); } - static void onEspNowMessage(const uint8_t *address, const uint8_t *msg, uint8_t len) { + static void onEspNowMessage(const uint8_t *address, const uint8_t *msg, uint8_t len, int8_t rssi) { if (msg) { if(len == sizeof(NodeMessage)) { - instance->onPeerData(address, (const NodeMessage*)msg, len, 0, true); + instance->onPeerData(address, (const NodeMessage*)msg, len, rssi, true); instance->onWizmote(address, (const wizmote_message*)msg, len); } else { #ifdef NODE_DEBUGGING diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 543aa6524d..860370b374 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -40,8 +40,10 @@ ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); typedef struct { uint8_t mac[6]; uint8_t len; + int8_t rssi; uint8_t data[WLED_ESPNOW_MAX_MESSAGE_LENGTH]; } QueuedNetworkMessage; +static_assert(sizeof(QueuedNetworkMessage) == WLED_ESPNOW_MAX_MESSAGE_LENGTH+8, "QueuedNetworkMessage larger than needed"); static_assert(WLED_ESPNOW_MAX_MESSAGE_LENGTH <= ESP_NOW_MAX_DATA_LEN, "WLED_ESPNOW_MAX_MESSAGE_LENGTH must be <= 250 bytes"); @@ -74,7 +76,7 @@ class ESPNOWBroadcastImpl : public ESPNOWBroadcast { buf = xRingbufferCreateNoSplit(sizeof(QueuedNetworkMessage), WLED_ESPNOW_MAX_QUEUED_MESSAGES); } - bool push(const uint8_t* mac, const uint8_t* data, uint8_t len); + bool push(const uint8_t* mac, const uint8_t* data, uint8_t len, int8_t rssi); QueuedNetworkMessage* pop() { size_t size = 0; @@ -185,7 +187,7 @@ void ESPNOWBroadcast::loop(size_t maxMessagesToProcess /*= 1*/) { if (msg) { auto callback = _rxCallbacks; while( *callback ) { - (*callback)(msg->mac, msg->data, msg->len); + (*callback)(msg->mac, msg->data, msg->len, msg->rssi); callback++; } espnowBroadcastImpl.queuedNetworkRingBuffer.popComplete(msg); @@ -345,8 +347,49 @@ void ESPNOWBroadcastImpl::onWiFiEvent(void* arg, esp_event_base_t event_base, in } } +typedef struct { + uint16_t frame_head; + uint16_t duration; + uint8_t destination_address[6]; + uint8_t source_address[6]; + uint8_t broadcast_address[6]; + uint16_t sequence_control; + + uint8_t category_code; + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t random_values[4]; + struct { + uint8_t element_id; // 0xdd + uint8_t lenght; // + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t type; // 4 + uint8_t version; + uint8_t body[0]; + } vendor_specific_content; +} __attribute__ ((packed)) espnow_frame_format_t; + +#ifdef ESPNOW_DEBUGGING +void logMACAddr(const uint8_t* mac) { + Serial.printf("%x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); +} +#endif + void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t *data, int len) { - if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len)) { + //const espnow_frame_format_t* espnow_data = (espnow_frame_format_t*)(data - sizeof (espnow_frame_format_t)); + const wifi_promiscuous_pkt_t* promiscuous_pkt = (wifi_promiscuous_pkt_t*)(data - sizeof (wifi_pkt_rx_ctrl_t) - sizeof (espnow_frame_format_t)); + int8_t rssi = 0; + try { + auto rssi32 = promiscuous_pkt->rx_ctrl.rssi; + rssi = rssi32 <= -128 ? -127 : rssi32 > 0 ? 0 : rssi32; + } catch(...) { + // be safe about accessing memory that isn't directly exposed to the callback + rssi = 0; + } + // Serial.printf( "RX from " ); + // logMACAddr(mac); + // Serial.printf( " %d:bytes rssi:%d\n", len, rssi); + + if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { if (len > sizeof(WLED_ESPNOW_MAX_MESSAGE_LENGTH)) { #ifdef ESPNOW_DEBUGGING Serial.printf("Receive to large of packet %d bytes. ignoring...\n", len); @@ -357,13 +400,14 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * } } -bool ESPNOWBroadcastImpl::QueuedNetworkRingBuffer::push(const uint8_t* mac, const uint8_t* data, uint8_t len) { +bool ESPNOWBroadcastImpl::QueuedNetworkRingBuffer::push(const uint8_t* mac, const uint8_t* data, uint8_t len, int8_t rssi) { #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) QueuedNetworkMessage msg[1]; if (len <= sizeof(msg->data)) { memcpy(msg->mac, mac, sizeof(msg->mac)); memcpy(&(msg->data), data, len); msg->len = len; + msg->rssi = rssi; if (pdTRUE == xRingbufferSend(buf, (void**)&msg, sizeof(*msg), 0)) { return true; @@ -377,6 +421,7 @@ bool ESPNOWBroadcastImpl::QueuedNetworkRingBuffer::push(const uint8_t* mac, cons memcpy(msg->mac, mac, sizeof(msg->mac)); memcpy(&(msg->data), data, len); msg->len = len; + msg->rssi = rssi; xRingbufferSendComplete(buf, msg); return true; } diff --git a/wled00/espnow_broadcast.h b/wled00/espnow_broadcast.h index 05dd490300..ae23b9ee3c 100644 --- a/wled00/espnow_broadcast.h +++ b/wled00/espnow_broadcast.h @@ -27,7 +27,7 @@ class ESPNOWBroadcast { bool send(const uint8_t* msg, size_t len); - typedef void (*receive_callback_t)(const uint8_t *sender, const uint8_t *data, uint8_t len); + typedef void (*receive_callback_t)(const uint8_t *sender, const uint8_t *data, uint8_t len, int8_t rssi); bool registerCallback( receive_callback_t callback ); bool removeCallback( receive_callback_t callback ); From 247cd2b303b65a451972096ce7c76854cfae54eb Mon Sep 17 00:00:00 2001 From: Craig Link <278699+craiglink@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:34:21 -0700 Subject: [PATCH 230/263] Rewrites the ESPNow code to use the IDF esp_now api vs QuickEspNow. (#30) This rewrites the ESPNow code to use the IDF esp_now api vs QuickEspNow. Additionally the WiFi / ESPNow code paths trigger based on WiFi events vs pooling at calls to Wifi.Disconnect() create a race condition. To eliminate other potential race conditions by events called from the WiFi task, a ESP32 RingBuffers are used to shared Wifi events and network messages with the main application task, reducing the risk of a race condition --- platformio_tubes.ini | 6 +- usermods/Tubes/controller.h | 3 +- usermods/Tubes/node.h | 269 ++++++++++++++----------- wled00/espnow_broadcast.cpp | 390 ++++++++++++++++++++++++++++++++++++ wled00/espnow_broadcast.h | 50 +++++ wled00/wled.cpp | 9 + 6 files changed, 600 insertions(+), 127 deletions(-) create mode 100644 wled00/espnow_broadcast.cpp create mode 100644 wled00/espnow_broadcast.h diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 2aa3886f1f..1a0df1fc2a 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -6,8 +6,8 @@ ; -- ir remotes ; For devices with those inputs use the [tubes] settings ; which adds those back in -platform = espressif32@6.7.0 -platform_packages = framework-arduinoespressif32 @ 3.20017.0 +platform = espressif32@6.8.1 +platform_packages = ;framework-arduinoespressif32 @ 3.20017.0 build_unflags = -D LOROL_LITTLEFS -D CONFIG_ASYNC_TCP_USE_WDT @@ -39,8 +39,6 @@ lib_deps = fastled/FastLED @ ^3.7.0 makuna/NeoPixelBus @ ^2.7.8 https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 - gmag11/QuickEspNow @ ^0.6.2 - gmag11/QuickDebug @ ^0.7.0 [tubes] extends = tubes_no_mic diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index d4d7338a02..dc24e0cb4a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -173,7 +173,6 @@ class PatternController : public MessageReceiver { void setup() { - node->setup(); EEPROM.begin(EEPSIZE); role = (ControllerRole)EEPROM.read(ROLE_EEPROM_LOCATION); if (role == 255) { @@ -205,6 +204,8 @@ class PatternController : public MessageReceiver { else strip.ablMilliampsMax = 1400; + node->setup(); + if (role >= MasterRole) { node->reset(3850 + role); // MASTER ID options.brightness = DEFAULT_MASTER_BRIGHTNESS; diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 45983bfa1d..5260b3a2a6 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -1,34 +1,25 @@ #pragma once #include -#if defined ESP32 -#include -#include -#elif defined ESP8266 -#include -#define WIFI_MODE_STA WIFI_STA -#else -#error "Unsupported platform" -#endif //ESP32 -#include - #include "global_state.h" +#include "espnow_broadcast.h" // #define NODE_DEBUGGING // #define RELAY_DEBUGGING #define TESTING_NODE_ID 0 #define CURRENT_NODE_VERSION 2 -#define BROADCAST_ADDR ESPNOW_BROADCAST_ADDRESS - -#define UPLINK_TIMEOUT 20000 // Time at which uplink is presumed lost -#define REBROADCAST_TIME 30000 // Time at which followers are presumed re-uplinked -#define WIFI_CHECK_RATE 2000 // Time at which we should check wifi status again #pragma pack(push,4) // set packing for consist transport across network // ideally this would have been pack 1, so we're actually wasting a // number of bytes across the network, but we've already shipped... +typedef enum{ + RECIPIENTS_ALL=0, // Send to all neighbors; non-followers will ignore + RECIPIENTS_ROOT=1, // Send to root for rebroadcasting downward, all will see + RECIPIENTS_INFO=2, // Send to all neighbors "FYI"; none will ignore +} MessageRecipients; + typedef uint16_t MeshId; typedef struct { @@ -37,14 +28,7 @@ typedef struct { uint8_t version = CURRENT_NODE_VERSION; } MeshNodeHeader; -typedef enum{ - RECIPIENTS_ALL=0, // Send to all neighbors; non-followers will ignore - RECIPIENTS_ROOT=1, // Send to root for rebroadcasting downward, all will see - RECIPIENTS_INFO=2, // Send to all neighbors "FYI"; none will ignore -} MessageRecipients; - #define MESSAGE_DATA_SIZE 64 - typedef struct { MeshNodeHeader header; MessageRecipients recipients; @@ -60,6 +44,7 @@ typedef struct { char message[40]; } NodeInfo; + const char *command_name(CommandId command) { switch (command) { case COMMAND_STATE: @@ -98,8 +83,9 @@ class LightNode { typedef enum{ NODE_STATUS_QUIET=0, - NODE_STATUS_STARTING=1, - NODE_STATUS_STARTED=2, + NODE_STATUS_RECEIVING, + NODE_STATUS_STARTED, + NODE_STATUS_MAX, } NodeStatus; NodeStatus status = NODE_STATUS_QUIET; @@ -107,8 +93,8 @@ class LightNode { switch (status) { case NODE_STATUS_QUIET: return PSTR(" (quiet)"); - case NODE_STATUS_STARTING: - return PSTR(" (starting)"); + case NODE_STATUS_RECEIVING: + return PSTR(" (receiving)"); case NODE_STATUS_STARTED: return PSTR(""); default: @@ -118,36 +104,19 @@ class LightNode { char node_name[20]; - Timer statusTimer; // Use this timer to initialize and check wifi status - Timer uplinkTimer; // When this timer ends, assume uplink is lost. - Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink - LightNode(MessageReceiver *r) : receiver(r) { - LightNode::instance = this; + instance = this; } -protected: - void onWifiConnect() { - if (status == NODE_STATUS_QUIET) - return; - - Serial.println("WiFi connected: stop broadcasting"); - quickEspNow.stop(); - status = NODE_STATUS_QUIET; - rebroadcastTimer.stop(); - statusTimer.start(WIFI_CHECK_RATE); - } + protected: - void onWifiDisconnect() { - if (status != NODE_STATUS_QUIET) - return; + const uint32_t STATUS_CHECK_RATE = 200; // Time at which we should check wifi status again + const uint32_t UPLINK_TIMEOUT = 20000; // Time at which uplink is presumed lost + const uint32_t REBROADCAST_TIME = 30000; // Time at which followers are presumed re-uplinked - Serial.println("WiFi disconnected: start broadcasting"); - WiFi.mode (WIFI_MODE_STA); - WiFi.disconnect(false, true); - quickEspNow.begin(1, WIFI_IF_STA); - start(); - } + Timer statusTimer; // Use this timer to initialize and check wifi status + Timer uplinkTimer; // When this timer ends, assume uplink is lost. + Timer rebroadcastTimer; // Until this timer ends, re-broadcast messages from uplink void onMeshChange() { sprintf(node_name, @@ -155,10 +124,11 @@ class LightNode { header.id, header.uplinkId ); - configuredAP(); + + configureAP(); } - void configuredAP() { + void configureAP() { #ifdef DEFAULT_WIFI strcpy(clientSSID, DEFAULT_WIFI); strcpy(clientPass, DEFAULT_WIFI_PASSWORD); @@ -172,47 +142,39 @@ class LightNode { apBehavior = AP_BEHAVIOR_BUTTON_ONLY; // Must press button for 6 seconds to get AP } - void start() { - // Initialization timer: wait for a bit before trying to broadcast. - // If this node's ID is high, it's more likely to be the leader, so wait less. - status = NODE_STATUS_STARTING; - statusTimer.start(3000 - header.id / 2); - rebroadcastTimer.stop(); - } - - void onPeerPing(MeshNodeHeader* node) { + void onPeerPing(const MeshNodeHeader& node) { // When receiving a message, if the IDs match, it's a conflict // Reset to create a new ID. - if (node->id == header.id) { + if (node.id == header.id) { Serial.println("Detected an ID conflict."); reset(); } // If the message arrives from a higher ID, switch into follower mode - if (node->id > header.uplinkId && node->id > header.id) { + if (node.id > header.uplinkId && node.id > header.id) { #ifdef RELAY_DEBUGGING // When debugging relay, pretend not to see any nodes above 0x800 if (node->id < 0x800) #endif - follow(node); + follow(&node); } // If the message arrived from our uplink, track that we're still linked. - if (node->id == header.uplinkId) { + if (node.id == header.uplinkId) { uplinkTimer.start(UPLINK_TIMEOUT); } // If a message indicates that another node is following this one, or // should be (it's not following anything, but this node's ID is higher) // enter or continue re-broadcasting mode. - if (node->uplinkId == header.id - || (node->uplinkId == 0 && node->id < header.id)) { - Serial.printf(" %03X/%03X is following me\n", node->id, node->uplinkId); + if (node.uplinkId == header.id + || (node.uplinkId == 0 && node.id < header.id)) { + Serial.printf(" %03X/%03X is following me", node.id, node.uplinkId); rebroadcastTimer.start(REBROADCAST_TIME); } } - void printMessage(NodeMessage* message, signed int rssi) { + void printMessage(const NodeMessage* message, signed int rssi) { Serial.printf("%03X/%03X %s", message->header.id, message->header.uplinkId, @@ -224,12 +186,11 @@ class LightNode { Serial.printf(" %ddB ", rssi); } - void onPeerData(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + void onPeerData(const uint8_t* address, const NodeMessage* message, uint8_t len, signed int rssi, bool broadcast) { // Ignore this message if it isn't a valid message payload. - if (len != sizeof(NodeMessage)) + if (len != sizeof(*message)) return; - NodeMessage* message = (NodeMessage*)data; // Ignore this message if it's the wrong version. if (message->header.version != header.version) { #ifdef NODE_DEBUGGING @@ -241,7 +202,7 @@ class LightNode { } // Track that another node exists, updating this node's understanding of the mesh. - onPeerPing(&message->header); + onPeerPing(message->header); bool ignore = false; switch (message->recipients) { @@ -290,7 +251,7 @@ class LightNode { // Execute the command auto valid = receiver->onCommand( message->command, - &message->data + const_cast(message->data) ); Serial.println(); @@ -300,17 +261,32 @@ class LightNode { // Re-broadcast the message if appropriate if (!rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { - message->header = header; - if (!isFollowing()) - message->recipients = RECIPIENTS_ALL; - broadcastMessage(message, true); + static NodeMessage msg; + memcpy(&msg, &message, len); + msg.header = header; + if (!isFollowing()) { + msg.recipients = RECIPIENTS_ALL; + } +#ifdef NODE_DEBUGGING + Serial.println("rebroadcast"); +#endif + broadcastMessage(&msg, true); } } void broadcastMessage(NodeMessage *message, bool is_rebroadcast=false) { // Don't broadcast anything if this node isn't active. - if (status != NODE_STATUS_STARTED) - return; + + if (status != NODE_STATUS_STARTED) { + if (status == NODE_STATUS_RECEIVING && statusTimer.every(STATUS_CHECK_RATE)) { + status = NODE_STATUS_STARTED; + statusTimer.stop(); + Serial.printf("LightNode %s\n", status_code()); + } else { + Serial.printf("broadcastMessage() - not started - %s\n", status_code()); + return; + } + } message->timebase = strip.timebase + millis(); #ifdef NODE_DEBUGGING @@ -319,17 +295,25 @@ class LightNode { Serial.println(); #endif - auto err = quickEspNow.send( - ESPNOW_BROADCAST_ADDRESS, - (uint8_t*)message, sizeof(*message) - ); - if (err) - Serial.printf(" *** Broadcast error %d\n", err); + __attribute__((unused)) auto err = espnowBroadcast.send((const uint8_t*)message, sizeof(*message)); + +#ifdef NODE_DEBUGGING + if (err != ESP_OK) { + Serial.printf("espnowBroadcast.send() failed: %d\n", err); + } else { + Serial.println("successful broadcast"); + } +#endif + } -public: + public: void sendCommand(CommandId command, void *data, uint8_t len) { + // if (!ESP_NOW.isStarted()) { + // Serial.println("SendCommand ESP Not Started!"); + // return; + // } if (len > MESSAGE_DATA_SIZE) { Serial.printf("Message is too big: %d vs %d\n", len, MESSAGE_DATA_SIZE); @@ -350,6 +334,9 @@ class LightNode { } message.command = command; memcpy(&message.data, data, len); +#ifdef NODE_DEBUGGING + Serial.println("sendCommand"); +#endif broadcastMessage(&message); } @@ -359,30 +346,27 @@ class LightNode { #else reset(); #endif - statusTimer.stop(); - quickEspNow.onDataRcvd(onDataReceived); - Serial.println("Mesh: ok"); + +#ifdef NODE_DEBUGGING + delay(2000); +#endif + + espnowBroadcast.registerCallback(onEspNowMessage); + + Serial.println("setup: ok"); } void update() { + + //process any wifi events to turn on/off ESPNode + checkESPNowState(); + // Check the last time we heard from the uplink node if (isFollowing() && uplinkTimer.ended()) { follow(NULL); } - if (statusTimer.every(WIFI_CHECK_RATE)) { - // The broadcast timer doubles as a timer for startup delay - // Once the initial timer has ended, mark this node as started - if (status == NODE_STATUS_STARTING) - status = NODE_STATUS_STARTED; - - // Check WiFi status and update node status if wifi changed - if (WiFi.isConnected()) - onWifiConnect(); - else - onWifiDisconnect(); - } } void reset(MeshId id = 0) { @@ -393,7 +377,7 @@ class LightNode { follow(NULL); } - void follow(MeshNodeHeader* node) { + void follow(const MeshNodeHeader* node) { if (node == NULL) { if (header.uplinkId != 0) { Serial.println("Uplink lost"); @@ -422,22 +406,54 @@ class LightNode { return header.uplinkId != 0; } -protected: +protected: + + void checkESPNowState() { + auto state = espnowBroadcast.getState(); + static auto prev = espnowBroadcast.STOPPED; + switch(state) { + case ESPNOWBroadcast::STOPPED: + if (NODE_STATUS_QUIET != status) { + Serial.printf("checkESPNowState() - %d node_status:%s\n", state, status_code()); + status = NODE_STATUS_QUIET; + rebroadcastTimer.stop(); + Serial.printf("LightNode %s\n", status_code()); + } + break; + case ESPNOWBroadcast::STARTING: {} + if ( state != prev ) { + Serial.printf("checkESPNowState() - %d node_status:%s\n", state, status_code()); + } + break; + case ESPNOWBroadcast::STARTED: + if (NODE_STATUS_QUIET == status) { + Serial.printf("checkESPNowState() - %d node_status:%s\n", state, status_code()); + status = NODE_STATUS_RECEIVING; + statusTimer.start(3000 - header.id / 2); + Serial.printf("LightNode %s\n", status_code()); + } + break; + default: + break; + } + prev = state; + } + typedef struct wizmote_message { - uint8_t program; // 0x91 for ON button, 0x81 for all others - uint8_t seq[4]; // Incremetal sequence number 32 bit unsigned integer LSB first - uint8_t byte5 = 32; // Unknown - uint8_t button; // Identifies which button is being pressed - uint8_t byte8 = 1; // Unknown, but always 0x01 - uint8_t byte9 = 100; // Unnkown, but always 0x64 - - uint8_t byte10; // Unknown, maybe checksum - uint8_t byte11; // Unknown, maybe checksum - uint8_t byte12; // Unknown, maybe checksum - uint8_t byte13; // Unknown, maybe checksum + uint8_t program; // 0x91 for ON button, 0x81 for all others + uint8_t seq[4]; // Incremetal sequence number 32 bit unsigned integer LSB first + uint8_t byte5 = 32; // Unknown + uint8_t button; // Identifies which button is being pressed + uint8_t byte8 = 1; // Unknown, but always 0x01 + uint8_t byte9 = 100; // Unnkown, but always 0x64 + + uint8_t byte10; // Unknown, maybe checksum + uint8_t byte11; // Unknown, maybe checksum + uint8_t byte12; // Unknown, maybe checksum + uint8_t byte13; // Unknown, maybe checksum } wizmote_message; - static void onWizmote(uint8_t* address, wizmote_message* data, uint8_t len) { + void onWizmote(const uint8_t* address, const wizmote_message* data, uint8_t len) { // First make sure this is a WizMote message. if (len != sizeof(wizmote_message) || data->byte8 != 1 || data->byte9 != 100 || data->byte5 != 32) return; @@ -448,13 +464,22 @@ class LightNode { return; last_seq = cur_seq; - instance->receiver->onButton(data->button); + receiver->onButton(data->button); } - static void onDataReceived(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - instance->onPeerData(address, data, len, rssi, broadcast); - onWizmote(address, (wizmote_message*)data, len); + static void onEspNowMessage(const uint8_t *address, const uint8_t *msg, uint8_t len) { + if (msg) { + if(len == sizeof(NodeMessage)) { + instance->onPeerData(address, (const NodeMessage*)msg, len, 0, true); + instance->onWizmote(address, (const wizmote_message*)msg, len); + } else { +#ifdef NODE_DEBUGGING + Serial.printf("wrong size QueueNodeMessage received %d\n", len); +#endif + } + } } + }; LightNode* LightNode::instance = nullptr; diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp new file mode 100644 index 0000000000..543aa6524d --- /dev/null +++ b/wled00/espnow_broadcast.cpp @@ -0,0 +1,390 @@ + +#ifndef WLED_DISABLE_ESPNOW_NEW +#include +#include + +#if defined ESP32 +#include +#include +#include + +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#else +#error "Unsupported platform" +#endif //ESP32 + +#include "espnow_broadcast.h" + +#ifdef ESP32 + +#include +#include +#include + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) +// Legacy Event Loop +ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); +#define WIFI_EVENT SYSTEM_EVENT +#define WIFI_EVENT_STA_START SYSTEM_EVENT_STA_START +#define WIFI_EVENT_STA_STOP SYSTEM_EVENT_STA_STOP +#define WIFI_EVENT_AP_START SYSTEM_EVENT_AP_START +#endif + +//#define ESPNOW_DEBUGGING + +#define BROADCAST_ADDR_ARRAY_INITIALIZER {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} +#define WLED_ESPNOW_WIFI_CHANNEL 1 + +typedef struct { + uint8_t mac[6]; + uint8_t len; + uint8_t data[WLED_ESPNOW_MAX_MESSAGE_LENGTH]; +} QueuedNetworkMessage; +static_assert(WLED_ESPNOW_MAX_MESSAGE_LENGTH <= ESP_NOW_MAX_DATA_LEN, "WLED_ESPNOW_MAX_MESSAGE_LENGTH must be <= 250 bytes"); + + +class ESPNOWBroadcastImpl : public ESPNOWBroadcast { + + friend ESPNOWBroadcast; + + std::atomic _state {STOPPED}; + STATE getState() { + return _state.load(); + } + + bool setupWiFi(); + + void start(); + + static esp_err_t onSystemEvent(void *ctx, system_event_t *event); + + static void onWiFiEvent(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); + + static void onESPNowRxCallback(const uint8_t *mac_addr, const uint8_t *data, int len); + + class QueuedNetworkRingBuffer { + protected: + //QueuedNetworkMessage messages[WLED_ESPNOW_MAX_QUEUED_MESSAGES]; + RingbufHandle_t buf = nullptr; + + public: + QueuedNetworkRingBuffer() { + buf = xRingbufferCreateNoSplit(sizeof(QueuedNetworkMessage), WLED_ESPNOW_MAX_QUEUED_MESSAGES); + } + + bool push(const uint8_t* mac, const uint8_t* data, uint8_t len); + + QueuedNetworkMessage* pop() { + size_t size = 0; + return (QueuedNetworkMessage*)xRingbufferReceive(buf, &size, 0); + } + + void popComplete(QueuedNetworkMessage* msg) { + vRingbufferReturnItem(buf, (void *)msg); + } + }; + + QueuedNetworkRingBuffer queuedNetworkRingBuffer {}; + +}; + +ESPNOWBroadcastImpl espnowBroadcastImpl {}; +#endif // ESP32 + +ESPNOWBroadcast espnowBroadcast {}; + + +ESPNOWBroadcast::STATE ESPNOWBroadcast::getState() { +#ifdef ESP32 + return espnowBroadcastImpl.getState(); +#else + return ESPNOWBroadcast::STOPPED; +#endif +} + +bool ESPNOWBroadcast::setup() { + + static bool setup = false; +#ifdef ESP32 + if (setup) { + return true; + } + + #ifdef ESPNOW_DEBUGGING + delay(2000); + #endif + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) + tcpip_adapter_init(); + esp_event_loop_init(ESPNOWBroadcastImpl::onSystemEvent, nullptr); +#else + + auto err = esp_event_loop_create_default(); + if ( ESP_OK != err && ESP_ERR_INVALID_STATE != err ) { + Serial.printf("esp_event_loop_create_default() err %d\n", err); + return false; + } + err = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); + if ( ESP_OK != err ) { + Serial.printf("esp_event_handler_instance_register(WIFI_EVENT_STA_START) err %d\n", err); + return false; + } + err = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_STOP, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); + if ( ESP_OK != err ) { + Serial.printf("esp_event_handler_instance_register(WIFI_EVENT_STA_STOP) err %d\n", err); + return false; + } + err = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_AP_START, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); + if ( ESP_OK != err ) { + Serial.printf("esp_event_handler_instance_register(WIFI_EVENT_AP_START) err %d\n", err); + return false; + } +#endif + + setup = espnowBroadcastImpl.setupWiFi(); +#endif //ESP32 + return setup; +} + +#ifdef ESP32 +bool ESPNOWBroadcastImpl::setupWiFi() { + Serial.println("ESPNOWBroadcast::setupWiFi()"); + + _state.exchange(STOPPED); + + // To enable ESPNow, we need to be in WIFI_STA mode + if ( !WiFi.mode(WIFI_STA) ) { + Serial.println("WiFi.mode() failed"); + return false; + } + // and not have the WiFi connect + // Calling discount with tigger an async Wifi Event + if ( !WiFi.disconnect(false, true) ) { + Serial.println("WiFi.disconnect() failed"); + return false; + } + + return true; +} +#endif //ESP32 + + +void ESPNOWBroadcast::loop(size_t maxMessagesToProcess /*= 1*/) { +#ifdef ESP32 + switch (espnowBroadcastImpl._state.load()) { + case ESPNOWBroadcast::STARTING: + // if WiFI is in starting state, actually stat ESPNow from our main task thread. + espnowBroadcastImpl.start(); + break; + case ESPNOWBroadcast::STARTED: { + auto ndx = maxMessagesToProcess; + while(ndx-- > 0) { + auto *msg = espnowBroadcastImpl.queuedNetworkRingBuffer.pop(); + if (msg) { + auto callback = _rxCallbacks; + while( *callback ) { + (*callback)(msg->mac, msg->data, msg->len); + callback++; + } + espnowBroadcastImpl.queuedNetworkRingBuffer.popComplete(msg); + } else { + break; + } + } + break; + } + default: + break; + } +#endif // ESP32 +} + +bool ESPNOWBroadcast::send(const uint8_t* msg, size_t len) { +#ifdef ESP32 + static const uint8_t broadcast[] = BROADCAST_ADDR_ARRAY_INITIALIZER; + return ESP_OK == esp_now_send(broadcast, msg, len); +#else + return false; +#endif +} + +bool ESPNOWBroadcast::registerCallback( ESPNOWBroadcast::receive_callback_t callback ) { + // last element is always null + size_t ndx; + for (ndx = 0; ndx < _rxCallbacksSize-1; ndx++) { + if (nullptr == _rxCallbacks[ndx]) { + _rxCallbacks[ndx] = callback; + break; + } + } + return ndx < _rxCallbacksSize; +} + +bool ESPNOWBroadcast::removeCallback( ESPNOWBroadcast::receive_callback_t callback ) { + size_t ndx; + for (ndx = 0; ndx < _rxCallbacksSize-1; ndx++) { + if (_rxCallbacks[ndx] == callback ) { + break; + } + } + + for (; ndx < _rxCallbacksSize-1; ndx++) { + _rxCallbacks[ndx] = _rxCallbacks[ndx+1]; + } + + return ndx < _rxCallbacksSize; + +} + +#ifdef ESP32 + +void ESPNOWBroadcastImpl::start() { + + Serial.println("starting ESPNow"); + + if ( WiFi.mode(WIFI_STA) ) { + auto status = WiFi.status(); + if ( status >= WL_DISCONNECTED ) { + if (esp_wifi_start() == ESP_OK) { + if (esp_now_init() == ESP_OK) { + if (esp_now_register_recv_cb(ESPNOWBroadcastImpl::onESPNowRxCallback) == ESP_OK) { + static esp_now_peer_info_t peer = { + BROADCAST_ADDR_ARRAY_INITIALIZER, + {0}, + WLED_ESPNOW_WIFI_CHANNEL, + WIFI_IF_STA, + false, + NULL + }; + + if (esp_now_add_peer(&peer) == ESP_OK) { + ESPNOWBroadcast::STATE starting {ESPNOWBroadcast::STARTING}; + if (_state.compare_exchange_strong(starting, ESPNOWBroadcast::STARTED)) { + return; + } else { +#ifdef ESPNOW_DEBUGGING + Serial.println("atomic state out of sync"); +#endif + } + } else { +#ifdef ESPNOW_DEBUGGING + Serial.println("esp_now_add_peer failed"); +#endif + } + } else { +#ifdef ESPNOW_DEBUGGING + Serial.println("esp_now_register_recv_cb failed"); +#endif + } + } else { +#ifdef ESPNOW_DEBUGGING + Serial.println("esp_now_init_init failed"); +#endif + } + } else { +#ifdef ESPNOW_DEBUGGING + Serial.println("esp_wifi_start failed"); +#endif + } + } else { +#ifdef ESPNOW_DEBUGGING + Serial.printf("WiFi.status not disconnected - %d\n", status); +#endif + } + } else { +#ifdef ESPNOW_DEBUGGING + Serial.println("WiFi.mode failed"); +#endif + } + Serial.println("restarting ESPNow"); + setupWiFi(); +} + +esp_err_t ESPNOWBroadcastImpl::onSystemEvent(void *ctx, system_event_t *event) { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) + onWiFiEvent(ctx, SYSTEM_EVENT, event->event_id, nullptr ); +#endif + return ESP_OK; +} + + +void ESPNOWBroadcastImpl::onWiFiEvent(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { + if ( event_base == WIFI_EVENT ) { + +#ifdef ESPNOW_DEBUGGING + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) + Serial.printf("WiFiEvent( %d )\n", event_id ); + #else + Serial.printf("WiFiEvent( %s )\n", WiFi.eventName((arduino_event_id_t)event_id) ); + #endif +#endif + switch (event_id) { + case WIFI_EVENT_STA_START: { + ESPNOWBroadcast::STATE stopped {ESPNOWBroadcast::STOPPED}; + espnowBroadcastImpl._state.compare_exchange_strong(stopped, ESPNOWBroadcast::STARTING); + break; + } + + case WIFI_EVENT_STA_STOP: + case WIFI_EVENT_AP_START: { + ESPNOWBroadcast::STATE started {ESPNOWBroadcast::STARTED}; + ESPNOWBroadcast::STATE starting {ESPNOWBroadcast::STARTING}; + if (espnowBroadcastImpl._state.compare_exchange_strong(started, ESPNOWBroadcast::STOPPED) || + espnowBroadcastImpl._state.compare_exchange_strong(starting, ESPNOWBroadcast::STOPPED)) { +#ifdef ESPNOW_DEBUGGING + Serial.println("WiFi connected: stop broadcasting"); +#endif + esp_now_unregister_recv_cb(); + esp_now_deinit(); + } + break; + } + } + } +} + +void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t *data, int len) { + if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len)) { + if (len > sizeof(WLED_ESPNOW_MAX_MESSAGE_LENGTH)) { +#ifdef ESPNOW_DEBUGGING + Serial.printf("Receive to large of packet %d bytes. ignoring...\n", len); +#endif + } else { + Serial.println("Failed to aquire ring buffer. Dropping network message"); + } + } +} + +bool ESPNOWBroadcastImpl::QueuedNetworkRingBuffer::push(const uint8_t* mac, const uint8_t* data, uint8_t len) { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) + QueuedNetworkMessage msg[1]; + if (len <= sizeof(msg->data)) { + memcpy(msg->mac, mac, sizeof(msg->mac)); + memcpy(&(msg->data), data, len); + msg->len = len; + + if (pdTRUE == xRingbufferSend(buf, (void**)&msg, sizeof(*msg), 0)) { + return true; + } + } + return false; +#else + QueuedNetworkMessage* msg = nullptr; + if (len <= sizeof(msg->data)) { + if (pdTRUE == xRingbufferSendAcquire(buf, (void**)&msg, sizeof(*msg), 0)) { + memcpy(msg->mac, mac, sizeof(msg->mac)); + memcpy(&(msg->data), data, len); + msg->len = len; + xRingbufferSendComplete(buf, msg); + return true; + } + } + return false; +#endif +} + +#endif // ESP32 + +#endif \ No newline at end of file diff --git a/wled00/espnow_broadcast.h b/wled00/espnow_broadcast.h new file mode 100644 index 0000000000..05dd490300 --- /dev/null +++ b/wled00/espnow_broadcast.h @@ -0,0 +1,50 @@ + +#pragma once + +#ifndef WLED_DISABLE_ESPNOW_NEW + +#include "const.h" + +#ifndef WLED_ESPNOW_MAX_QUEUED_MESSAGES +#define WLED_ESPNOW_MAX_QUEUED_MESSAGES 6 +#endif + +#ifndef WLED_ESPNOW_MAX_MESSAGE_LENGTH +#define WLED_ESPNOW_MAX_MESSAGE_LENGTH 250 +#endif + +#ifndef WLED_ESPNOW_MAX_REGISTERED_CALLBACKS +#define WLED_ESPNOW_MAX_REGISTERED_CALLBACKS WLED_MAX_USERMODS+1 +#endif + +class ESPNOWBroadcast { + + public: + + bool setup(); + + void loop(size_t maxMessagesToProcess = WLED_ESPNOW_MAX_QUEUED_MESSAGES); + + bool send(const uint8_t* msg, size_t len); + + typedef void (*receive_callback_t)(const uint8_t *sender, const uint8_t *data, uint8_t len); + bool registerCallback( receive_callback_t callback ); + bool removeCallback( receive_callback_t callback ); + + enum STATE { + STOPPED = 0, + STARTING, + STARTED, + MAX + }; + + STATE getState(); + + protected: + receive_callback_t _rxCallbacks[WLED_ESPNOW_MAX_REGISTERED_CALLBACKS] = {0}; + static constexpr size_t _rxCallbacksSize = sizeof(_rxCallbacks)/sizeof(_rxCallbacks[0]); + +}; + +extern ESPNOWBroadcast espnowBroadcast; +#endif \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 8ba6b1a565..5c64aa3b46 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -2,6 +2,7 @@ #include "wled.h" #include "wled_ethernet.h" #include +#include "espnow_broadcast.h" #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) #include "soc/soc.h" @@ -499,6 +500,10 @@ pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), Pin initServer(); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); +#ifndef WLED_DISABLE_ESPNOW_NEW + espnowBroadcast.setup(); +#endif + enableWatchdog(); #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) @@ -808,6 +813,10 @@ void WLED::handleConnection() return; } +#ifndef WLED_DISABLE_ESPNOW_NEW + espnowBroadcast.loop(); +#endif + // reconnect WiFi to clear stale allocations if heap gets too low if (now - heapTime > 5000) { uint32_t heap = ESP.getFreeHeap(); From f9a3fc8078c9d06b575ea65473302a70f3a2e467 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Fri, 9 Aug 2024 09:39:10 -0700 Subject: [PATCH 231/263] add separate debug flag for rx callback logging --- wled00/espnow_broadcast.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 85dadef747..946745e16e 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -32,7 +32,8 @@ ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); #define WIFI_EVENT_AP_START SYSTEM_EVENT_AP_START #endif -// #define ESPNOW_DEBUGGING +//#define ESPNOW_DEBUGGING +//#define ESNOW_CALLBACK_DEBUGGING // Serial is called from multiple threads #define BROADCAST_ADDR_ARRAY_INITIALIZER {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define WLED_ESPNOW_WIFI_CHANNEL 1 @@ -385,12 +386,6 @@ typedef struct { } vendor_specific_content; } __attribute__ ((packed)) espnow_frame_format_t; -#ifdef ESPNOW_DEBUGGING -void logMACAddr(const uint8_t* mac) { - Serial.printf("%x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); -} -#endif - void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t *data, int len) { //const espnow_frame_format_t* espnow_data = (espnow_frame_format_t*)(data - sizeof (espnow_frame_format_t)); const wifi_promiscuous_pkt_t* promiscuous_pkt = (wifi_promiscuous_pkt_t*)(data - sizeof (wifi_pkt_rx_ctrl_t) - sizeof (espnow_frame_format_t)); @@ -406,10 +401,13 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { Serial.printf("Failed to queue message (%d bytes) to ring buffer. Dropping message\n", len); } else { -#ifdef ESPNOW_DEBUGGING - Serial.printf("Received %d bytes from "); - logMACAddr(mac); - Serial.printf(" RSSI %d\n", len, rssi); +#ifdef ESPNOW_CALLBACK_DEBUGGING + char buf[128]; + sprintf(buf, "Received %d bytes from %x:%x:%x:%x:%x:%x RSSI %d", len, + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + rssi + ); + Serial.println(buf); #endif } } From d59f2f013fe0557dc0ab2ab03afa17247724a2ed Mon Sep 17 00:00:00 2001 From: Craig Link Date: Fri, 9 Aug 2024 09:45:05 -0700 Subject: [PATCH 232/263] remove extra log line --- usermods/Tubes/node.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 7ad6a72d38..f5362fb0dc 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -276,10 +276,6 @@ class LightNode { void broadcastMessage(NodeMessage *message, bool is_rebroadcast=false) { // Don't broadcast anything if this node isn't active. -#ifdef NODE_DEBUGGING - Serial.printf("broadcastMessage() - %s %s\n", status_code(), statusTimer.ended() ? "True" : "False"); -#endif - if (status != NODE_STATUS_STARTED) { if (status == NODE_STATUS_RECEIVING && statusTimer.ended()) { status = NODE_STATUS_STARTED; From deb35f43bd4ddf50fb1daf47467da88aab3e020e Mon Sep 17 00:00:00 2001 From: Craig Link <278699+craiglink@users.noreply.github.com> Date: Wed, 7 Aug 2024 08:01:50 -0700 Subject: [PATCH 233/263] add esp32-c3-athom device (#34) --- platformio_tubes.ini | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 1a0df1fc2a..1d1aa0c233 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -125,3 +125,34 @@ lib_ignore = lib_deps = ${tubes_no_mic.lib_deps} +# ------------------------------------------------------------------------------ +# ESP32 C3 Athom +# ------------------------------------------------------------------------------ +[env:esp32-c3-athom] +extends = env:esp32c3dev +lib_ignore = IRremoteESP8266 + ${env:esp32c3dev.lib_ignore} +build_flags = ${env:esp32c3dev.build_flags} + -D LEDPIN=10 + -D BTNPIN=9 + -D WLED_DISABLE_INFRARED + -D IRTYPE=0 + +[env:esp32-c3-athom_tubes] +extends = env:esp32-c3-athom +platform = ${tubes_no_mic.platform} +platform_packages = ${tubes_no_mic.platform_packages} +build_unflags = ${env:esp32-c3-athom.build_unflags} + ${tubes_no_mic.build_unflags} +build_flags = + ${tubes_no_mic.build_flags} + -D WLED_WATCHDOG_TIMEOUT=0 +; -D LOLIN_WIFI_FIX ; seems to work much better with this + -D LEDPIN=10 + -D BTNPIN=9 + -D FASTLED_ESP32_SPI_BUS=HSPI + -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 +lib_ignore = ${env:esp32-c3-athom.lib_ignore} + ${tubes_no_mic.lib_ignore} +lib_deps = + ${tubes_no_mic.lib_deps} From 568df6ec196391a876656e42875a0d6bc4742551 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Tue, 13 Aug 2024 16:28:49 -0700 Subject: [PATCH 234/263] reduce unnecessary use of heap to create some singleton objects and instead use them by reference and appropraitely used a const reference in the debug class --- usermods/Tubes/Tubes.h | 28 ++++++------ usermods/Tubes/controller.h | 85 +++++++++++++++++++------------------ usermods/Tubes/debug.h | 54 +++++++++++------------ usermods/Tubes/led_strip.h | 2 +- usermods/Tubes/master.h | 16 +++---- usermods/Tubes/node.h | 5 ++- usermods/Tubes/timer.h | 4 +- 7 files changed, 93 insertions(+), 101 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 1483347016..1b4b4214be 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -9,7 +9,6 @@ #include "FX.h" -#include "beats.h" #include "virtual_strip.h" #include "led_strip.h" #include "master.h" @@ -24,10 +23,9 @@ class TubesUsermod : public Usermod { private: - BeatController beats; - PatternController controller = PatternController(MAX_REAL_LEDS, &beats); - DebugController debug = DebugController(&controller); - Master *master = nullptr; + PatternController controller = PatternController(MAX_REAL_LEDS); + DebugController debug = DebugController(controller); + Master master = Master(controller); bool isLegacy = false; void randomize() { @@ -59,11 +57,9 @@ class TubesUsermod : public Usermod { // Start timing globalTimer.setup(); - beats.setup(); controller.setup(); if (controller.isMasterRole()) { - master = new Master(&controller); - master->setup(); + master.setup(); } debug.setup(); } @@ -76,23 +72,23 @@ class TubesUsermod : public Usermod { globalTimer.update(); - if (master) - master->update(); - beats.update(); + if (controller.isMasterRole()) { + master.update(); + } controller.update(); debug.update(); // Draw after everything else is done - controller.led_strip->update(); + controller.led_strip.update(); } - void handleOverlayDraw() - { + void handleOverlayDraw() { // Draw effects layers over whatever WLED is doing. controller.handleOverlayDraw(); debug.handleOverlayDraw(); - if (master) - master->handleOverlayDraw(); + if (controller.isMasterRole()) { + master.handleOverlayDraw(); + } // When AP mode is on, make sure it's obvious // Blink when there's a connected client diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index dc24e0cb4a..54378e60c9 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -131,10 +131,10 @@ class PatternController : public MessageReceiver { #ifdef USELCD Lcd *lcd; #endif - LEDs *led_strip; - BeatController *beats; - Effects *effects; - LightNode *node; + LEDs led_strip; + BeatController beats; + Effects effects; + LightNode node; ControllerOptions options; char key_buffer[20] = {0}; @@ -146,16 +146,13 @@ class PatternController : public MessageReceiver { // When a pattern is boring, spice it up a bit with more effects bool isBoring = false; - PatternController(uint8_t num, BeatController *b) : num_leds(num), beats(b) { + PatternController(uint8_t num) : + num_leds(num), led_strip(num), node(this) { #ifdef USELCD lcd = new Lcd(); #endif - led_strip = new LEDs(num_leds); - effects = new Effects(); - node = new LightNode(this); - // mesh = new BLEMeshNode(this); - for (uint8_t i=0; i < NUM_VSTRIPS; i++) { + for (auto i=0; i < NUM_VSTRIPS; i++) { #ifdef DOUBLED vstrips[i] = new VirtualStrip(num_leds * 2 + 1); #else @@ -164,7 +161,7 @@ class PatternController : public MessageReceiver { } } - bool isMasterRole() { + bool isMasterRole() const { #if defined(GOLDEN) || defined(CHRISTMAS) return true; #endif @@ -204,10 +201,12 @@ class PatternController : public MessageReceiver { else strip.ablMilliampsMax = 1400; - node->setup(); + + beats.setup(); + node.setup(); if (role >= MasterRole) { - node->reset(3850 + role); // MASTER ID + node.reset(3850 + role); // MASTER ID options.brightness = DEFAULT_MASTER_BRIGHTNESS; } else if (role >= LegacyRole) { options.brightness = DEFAULT_TUBE_BRIGHTNESS; @@ -217,7 +216,7 @@ class PatternController : public MessageReceiver { options.brightness = DEFAULT_TUBE_BRIGHTNESS; } #if defined(GOLDEN) || defined(CHRISTMAS) - node->reset(0xFFF); + node.reset(0xFFF); #endif options.debugging = false; load_options(options, true); @@ -300,11 +299,11 @@ class PatternController : public MessageReceiver { selectTimer.start(20000); } - bool isSelecting() { + bool isSelecting() const { return !selectTimer.ended(); } - bool isSelected() { + bool isSelected() const { return updater.status == Ready; } @@ -365,8 +364,10 @@ class PatternController : public MessageReceiver { { read_keys(); + beats.update(); + // Update the mesh - node->update(); + node.update(); // Update sound meter sound.update(); @@ -407,7 +408,7 @@ class PatternController : public MessageReceiver { // Update current status if (updateTimer.every(STATUS_UPDATE_PERIOD)) { // Transmit less often when following - if (!node->isFollowing() || random(0, 4) == 0) { + if (!node.isFollowing() || random(0, 4) == 0) { send_update(); } } @@ -440,7 +441,7 @@ class PatternController : public MessageReceiver { if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer for (int i = 0; i < length; i++) { - CRGB c = led_strip->getPixelColor(i); + CRGB c = led_strip.getPixelColor(i); if (fader > 0) { CRGB color2 = strip.getPixelColor(i); uint8_t r = blend8(c.r, color2.r, fader); @@ -468,7 +469,7 @@ class PatternController : public MessageReceiver { // Draw effects layers over whatever WLED is doing. // But not in manual (WLED) mode if (!patternOverride) { - effects->draw(&strip); + effects.draw(&strip); } // Make the art half-size if it has a small number of pixels @@ -502,20 +503,20 @@ class PatternController : public MessageReceiver { } void restart_phrase() { - beats->start_phrase(); + beats.start_phrase(); update_beat(); send_update(); } void set_phrase_position(uint8_t pos) { - beats->sync(beats->bpm, (beats->frac & -0xFFF) + (pos<<8)); + beats.sync(beats.bpm, (beats.frac & -0xFFF) + (pos<<8)); update_beat(); send_update(); } void set_tapped_bpm(accum88 bpm, uint8_t pos=15) { // By default, restarts at 15th beat - because this is the end of a tap - beats->sync(bpm, (beats->frac & -0xFFF) + (pos<<8)); + beats.sync(bpm, (beats.frac & -0xFFF) + (pos<<8)); update_beat(); send_update(); } @@ -525,7 +526,7 @@ class PatternController : public MessageReceiver { if (new_bpm == 0) new_bpm = current_state.bpm>>8 >= 123 ? 120<<8 : 125<<8; - if (node->isFollowing()) { + if (node.isFollowing()) { // Send a request up to ROOT broadcast_bpm(new_bpm); } else { @@ -534,8 +535,8 @@ class PatternController : public MessageReceiver { } void update_beat() { - current_state.bpm = next_state.bpm = beats->bpm; - current_state.beat_frame = particle_beat_frame = beats->frac; // (particle_beat_frame is a hack) + current_state.bpm = next_state.bpm = beats.bpm; + current_state.beat_frame = particle_beat_frame = beats.frac; // (particle_beat_frame is a hack) if (current_state.bpm>>8 <= 118) // Hip hop / ghettofunk energy = MediumEnergy; else if (current_state.bpm>>8 >= 125) // House & breaks @@ -708,7 +709,7 @@ class PatternController : public MessageReceiver { current_state.print(); Serial.println(); - effects->load(current_state.effect_params); + effects.load(current_state.effect_params); } // Choose the effect to display at the next effect cycle @@ -793,7 +794,7 @@ class PatternController : public MessageReceiver { load_options(options); // The master controls all followers - if (!node->isFollowing()) + if (!node.isFollowing()) broadcast_options(); } @@ -804,7 +805,7 @@ class PatternController : public MessageReceiver { load_options(options); // The master controls all followers - if (!node->isFollowing()) + if (!node.isFollowing()) broadcast_options(); } @@ -886,10 +887,10 @@ class PatternController : public MessageReceiver { wled_fader = vstrip->fader; vstrip->update(beat_frame, beat_pulse); - vstrip->blend(led_strip->leds, led_strip->num_leds, options.brightness, vstrip == first_strip); + vstrip->blend(led_strip.leds, led_strip.num_leds, options.brightness, vstrip == first_strip); } - effects->update(first_strip, beat_frame, (BeatPulse)beat_pulse); + effects.update(first_strip, beat_frame, (BeatPulse)beat_pulse); } virtual void acknowledge() { @@ -987,7 +988,7 @@ class PatternController : public MessageReceiver { return; case 's': - beats->start_phrase(); + beats.start_phrase(); update_beat(); send_update(); return; @@ -1031,7 +1032,7 @@ class PatternController : public MessageReceiver { case 'i': Serial.printf("Reset! ID -> %03X\n", arg >> 4); - node->reset(arg >> 4); + node.reset(arg >> 4); return; case 'U': @@ -1127,31 +1128,31 @@ class PatternController : public MessageReceiver { } void broadcast_action(Action& action) { - if (!node->isFollowing()) { + if (!node.isFollowing()) { onAction(&action); } - node->sendCommand(COMMAND_ACTION, &action, sizeof(Action)); + node.sendCommand(COMMAND_ACTION, &action, sizeof(Action)); } void broadcast_info(NodeInfo *info) { - node->sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); + node.sendCommand(COMMAND_INFO, &info, sizeof(NodeInfo)); } void broadcast_state() { - node->sendCommand(COMMAND_STATE, ¤t_state, sizeof(TubeStates)); + node.sendCommand(COMMAND_STATE, ¤t_state, sizeof(TubeStates)); } void broadcast_options() { - node->sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); + node.sendCommand(COMMAND_OPTIONS, &options, sizeof(options)); } void broadcast_autoupdate() { - node->sendCommand(COMMAND_UPGRADE, &updater.current_version, sizeof(updater.current_version)); + node.sendCommand(COMMAND_UPGRADE, &updater.current_version, sizeof(updater.current_version)); } void broadcast_bpm(accum88 bpm) { // Hacked in feature: request a new BPM - node->sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); + node.sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); } virtual bool onCommand(CommandId command, void *data) { @@ -1184,7 +1185,7 @@ class PatternController : public MessageReceiver { load_pattern(state); load_palette(state); load_effect(state); - beats->sync(state.bpm, state.beat_frame); + beats.sync(state.bpm, state.beat_frame); return true; } @@ -1312,7 +1313,7 @@ class PatternController : public MessageReceiver { } virtual bool onButton(uint8_t button_id) { - bool isMaster = !this->node->isFollowing(); + bool isMaster = !this->node.isFollowing(); switch (button_id) { case WIZMOTE_BUTTON_ON: diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index ef7fbbdd62..afdb0bc910 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -24,17 +24,11 @@ std::string formatted_time(long ms) { class DebugController { public: - PatternController *controller; - LEDs *led_strip; - LightNode *node; + const PatternController& controller; uint32_t lastPhraseTime; uint32_t lastFrame; - DebugController(PatternController *c) : controller(c) - { - led_strip = controller->led_strip; - node = controller->node; - } + DebugController(const PatternController& c) : controller(c) {} void setup() { @@ -49,8 +43,8 @@ class DebugController { auto knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); auto knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); Serial.printf("\n=== %s%s WiFi[ch%d] %s IP: %u.%u.%u.%u Free memory: %d space: %u/%u Uptime: %s\n", - controller->node->node_name, - controller->node->status_code(), + controller.node.node_name, + controller.node.status_code(), WiFi.channel(), knownSsid.c_str(), knownIp[0], @@ -64,15 +58,15 @@ class DebugController { ); Serial.printf("=== Controller: "); - if (controller->isMasterRole()) { + if (controller.isMasterRole()) { Serial.print("PRIMARY "); } - if (controller->sound.active) { + if (controller.sound.active) { Serial.print("SOUND "); } Serial.printf("role=%d power_save=%d\n", - controller->role, - controller->power_save + controller.role, + controller.power_save ); // Dump WLED status @@ -89,24 +83,24 @@ class DebugController { seg.speed, seg.intensity ); - if (controller->patternOverride) { - Serial.printf(" (PATTERN %d)", controller->patternOverride); + if (controller.patternOverride) { + Serial.printf(" (PATTERN %d)", controller.patternOverride); } else { - Serial.printf(" at %d", controller->wled_fader); + Serial.printf(" at %d", controller.wled_fader); } - if (controller->paletteOverride) { - Serial.printf(" (PALETTE %d)", controller->paletteOverride); + if (controller.paletteOverride) { + Serial.printf(" (PALETTE %d)", controller.paletteOverride); } Serial.println(); Serial.printf("=== firmware: v%d from SSID %s %u.%u.%u.%u OTA=%d\n\n", - controller->updater.current_version.version, - controller->updater.current_version.ssid, - controller->updater.current_version.host[0], - controller->updater.current_version.ssid[1], - controller->updater.current_version.ssid[2], - controller->updater.current_version.ssid[3], - controller->updater.status + controller.updater.current_version.version, + controller.updater.current_version.ssid, + controller.updater.current_version.host[0], + controller.updater.current_version.ssid[1], + controller.updater.current_version.ssid[2], + controller.updater.current_version.ssid[3], + controller.updater.status ); } @@ -115,16 +109,16 @@ class DebugController { void handleOverlayDraw() { // Show the beat on the master OR if debugging - if (controller->options.debugging) { + if (controller.options.debugging) { uint16_t num_leds = strip.getLengthTotal(); - uint8_t p1 = (controller->current_state.beat_frame >> 8) % 16; + uint8_t p1 = (controller.current_state.beat_frame >> 8) % 16; strip.setPixelColor(p1, CRGB::White); - uint8_t p2 = scale8(controller->node->header.id>>4, num_leds-1); + uint8_t p2 = scale8(controller.node.header.id>>4, num_leds-1); strip.setPixelColor(p2, CRGB::Yellow); - uint8_t p3 = scale8(controller->node->header.uplinkId>>4, num_leds-1); + uint8_t p3 = scale8(controller.node.header.uplinkId>>4, num_leds-1); if (p3 == p2) { strip.setPixelColor(p3, CRGB::Green); } else { diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 7edf523956..80d461c15a 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -44,7 +44,7 @@ class LEDs { } } - CRGB getPixelColor(uint8_t pos) { + CRGB getPixelColor(uint8_t pos) const { if (pos > num_leds) return CRGB::Black; return leds[pos]; diff --git a/usermods/Tubes/master.h b/usermods/Tubes/master.h index 3ae0b2303a..4efcb33a6e 100644 --- a/usermods/Tubes/master.h +++ b/usermods/Tubes/master.h @@ -25,10 +25,10 @@ class Master { uint8_t palette_mode = false; uint8_t palette_id = 0; - PatternController *controller; + PatternController& controller; Button button[5]; - Master(PatternController *c) : controller(c) { }; + Master(PatternController& c) : controller(c) { }; void setup() { button[0].setup(BUTTON_PIN_1); @@ -77,7 +77,7 @@ class Master { if (b == 4) { Serial.println((char *)F("Skip >>")); - controller->force_next(); + controller.force_next(); ok(); return; } @@ -95,7 +95,7 @@ class Master { #ifdef EXTRA_STUFF if (b == 2) { if (palette_mode) - controller->_load_palette(palette_id); + controller._load_palette(palette_id); palette_mode = false; } #endif @@ -151,20 +151,20 @@ class Master { else if (frac > 128) bpm += (256-frac) / 2; - controller->set_tapped_bpm(bpm); + controller.set_tapped_bpm(bpm); ok(); } else if (taps >= 2) { - controller->set_tapped_bpm(controller->current_state.bpm, taps-1); + controller.set_tapped_bpm(controller.current_state.bpm, taps-1); } } - void updateStatus(PatternController *controller) { + void updateStatus(const PatternController& controller) { if (taps) { displayProgress(taps); } else if (palette_mode) { displayPalette(background); } else { - uint8_t beat_pos = (controller->current_state.beat_frame >> 8) % 16; + uint8_t beat_pos = (controller.current_state.beat_frame >> 8) % 16; strip.setPixelColor(15 - beat_pos, CRGB::White); } } diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index f5362fb0dc..82799f678d 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -89,7 +89,7 @@ class LightNode { } NodeStatus; NodeStatus status = NODE_STATUS_QUIET; - PGM_P status_code() { + PGM_P status_code() const { switch (status) { case NODE_STATUS_QUIET: return PSTR(" (quiet)"); @@ -469,10 +469,11 @@ class LightNode { if (msg) { if(len == sizeof(NodeMessage)) { instance->onPeerData(address, (const NodeMessage*)msg, len, rssi, true); + } else if(len == sizeof(wizmote_message)) { instance->onWizmote(address, (const wizmote_message*)msg, len); } else { #ifdef NODE_DEBUGGING - Serial.printf("wrong size QueueNodeMessage received %d\n", len); + Serial.printf("wrong size EspNowMessage received %d\n", len); #endif } } diff --git a/usermods/Tubes/timer.h b/usermods/Tubes/timer.h index 87b5de71b3..f888a12316 100644 --- a/usermods/Tubes/timer.h +++ b/usermods/Tubes/timer.h @@ -43,7 +43,7 @@ class Timer { start(0); } - uint32_t since_mark() { + uint32_t since_mark() const { if (globalTimer.now_millis < markTime) return 0; return globalTimer.now_millis - markTime; @@ -54,7 +54,7 @@ class Timer { markTime += duration_ms; } - bool ended() { + bool ended() const { return globalTimer.now_millis > markTime; } From 421063a4a80b6408ad3b96a7730800fe12f6b47b Mon Sep 17 00:00:00 2001 From: Craig Link Date: Wed, 14 Aug 2024 09:46:38 -0700 Subject: [PATCH 235/263] Make particles arrary static and not use heap Avoids heap fragmentation and potential crashes Leverages C++ std::move for creating new particles --- usermods/Tubes/effects.h | 45 +++++++++++------------ usermods/Tubes/particle.h | 75 +++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 62 deletions(-) diff --git a/usermods/Tubes/effects.h b/usermods/Tubes/effects.h index baceddafec..a23254850b 100644 --- a/usermods/Tubes/effects.h +++ b/usermods/Tubes/effects.h @@ -6,44 +6,44 @@ void addGlitter(CRGB color=CRGB::White, PenMode pen=Draw) { - addParticle(new Particle(random16(), color, pen, 128)); + addParticle(Particle(random16(), color, pen, 128)); } void addSpark(CRGB color=CRGB::White, PenMode pen=Draw) { - Particle *particle = new Particle(random16(), color, pen, 64); + Particle particle {random16(), color, pen, 64}; uint8_t r = random8(); if (r > 128) - particle->velocity = r; + particle.velocity = r; else - particle->velocity = -(128 + r); - addParticle(particle); + particle.velocity = -(128 + r); + addParticle(std::move(particle)); } void addBeatbox(CRGB color=CRGB::White, PenMode pen=Draw) { - Particle *particle = new Particle(random16(), color, pen, 256, drawBeatbox); - addParticle(particle); + Particle particle {random16(), color, pen, 256, drawBeatbox}; + addParticle(std::move(particle)); } void addBubble(CRGB color=CRGB::White, PenMode pen=Draw) { - Particle *particle = new Particle(random16(), color, pen, 1024, drawPop); - particle->velocity = random16(0, 40) - 20; - addParticle(particle); + Particle particle {random16(), color, pen, 1024, drawPop}; + particle.velocity = random16(0, 40) - 20; + addParticle(std::move(particle)); } void addFlash(CRGB color=CRGB::Blue, PenMode pen=Draw) { - addParticle(new Particle(random16(), color, pen, 256, drawFlash)); + addParticle(Particle(random16(), color, pen, 256, drawFlash)); } void addDrop(CRGB color, PenMode pen=Draw) { - Particle *particle = new Particle(65535, color, pen, 360); - particle->velocity = -500; - particle->gravity = -10; - addParticle(particle); + Particle particle {65535, color, pen, 360}; + particle.velocity = -500; + particle.gravity = -10; + addParticle(std::move(particle)); } class Effects { @@ -101,13 +101,11 @@ class Effects { void animate(BeatFrame_24_8 frame, uint8_t beat_pulse) { unsigned int len = numParticles; - for (unsigned i=len; i > 0; i--) { - Particle *particle = particles[i-1]; - - particle->update(frame); - if (particle->age > particle->lifetime) { - removeParticle(i-1); - continue; + for (unsigned i=len; i > 0; --i) { + Particle& particle = particles[i]; + particle.update(frame); + if (particle.age > particle.lifetime) { + removeParticle(i); } } } @@ -115,8 +113,7 @@ class Effects { void draw(WS2812FX* leds) { uint8_t len = numParticles; for (uint8_t i=0; idrawFn(particle, leds); + particles[i].draw(leds); } } diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index 9005e2ef4f..db4f773f83 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -11,27 +11,26 @@ class Particle; -typedef void (*ParticleFn)(Particle *particle, WS2812FX* leds); +typedef void (*ParticleFn)(Particle& particle, WS2812FX* leds); uint8_t particleVolume = DEFAULT_PARTICLE_VOLUME; -extern void drawPoint(Particle *particle, WS2812FX* leds); -extern void drawFlash(Particle *particle, WS2812FX* leds); +extern void drawPoint(Particle& particle, WS2812FX* leds); class Particle { public: - Particle(uint16_t pos, CRGB c=CRGB::White, PenMode p=Draw, uint32_t life=20000, ParticleFn fn=drawPoint) : + Particle(uint16_t pos = 0, CRGB c=CRGB::White, PenMode p=Draw, uint32_t life=20000, ParticleFn fn=drawPoint) : + born(0), + lifetime(life), age(0), color(c), + pen(p), brightness(192<<8), drawFn(fn), + position(pos), velocity(0), gravity(0) - { - pen = p; - position = pos; - lifetime = life; - }; + {}; BeatFrame_24_8 born; BeatFrame_24_8 lifetime; @@ -45,7 +44,7 @@ class Particle { uint16_t position; int16_t velocity; int16_t gravity; - void (*die_fn)(Particle *particle) = NULL; +// void (*die_fn)(Particle *particle) = NULL; #ifdef PARTICLE_PALETTES CRGBPalette16 palette; // 48 bytes per particle!? @@ -104,6 +103,10 @@ class Particle { return CRGB(r,g,b); } + void draw(WS2812FX* leds) { + drawFn(*this, leds); + } + void draw_with_pen(WS2812FX* leds, int pos, CRGB color) { CRGB c = CRGB(strip.getPixelColor(pos)); CRGB new_color; @@ -165,7 +168,7 @@ class Particle { }; -Particle* particles[MAX_PARTICLES]; +Particle particles[MAX_PARTICLES]; BeatFrame_24_8 particle_beat_frame; uint8_t numParticles = 0; @@ -173,11 +176,7 @@ void removeParticle(uint8_t i) { if (i >= numParticles) return; - // Free the memory of the old particle - Particle *old_particle = particles[i]; - delete old_particle; - - // Reset the current free particle + // coalesce current particle list int rest = numParticles - i; if (rest > 0) { memmove(&particles[i], &particles[i+1], sizeof(particles[0]) * rest); @@ -186,32 +185,32 @@ void removeParticle(uint8_t i) { numParticles -= 1; } -void addParticle(Particle *particle) { - particle->born = particle_beat_frame; +void addParticle(Particle&& particle) { + particle.born = particle_beat_frame; if (numParticles >= MAX_PARTICLES) { removeParticle(0); } particles[numParticles++] = particle; } -void drawFlash(Particle *particle, WS2812FX* leds) { +void drawFlash(Particle& particle, WS2812FX* leds) { auto num_leds = leds->getLengthTotal(); - uint16_t age_frac = particle->age_frac16(particle->age); - CRGB c = particle->color_at(age_frac); + uint16_t age_frac = particle.age_frac16(particle.age); + CRGB c = particle.color_at(age_frac); for (int pos = 0; pos < num_leds; pos++) { - particle->draw_with_pen(leds, pos, c); + particle.draw_with_pen(leds, pos, c); } } -void drawPoint(Particle *particle, WS2812FX* leds) { - uint16_t age_frac = particle->age_frac16(particle->age); - CRGB c = particle->color_at(age_frac); +void drawPoint(Particle& particle, WS2812FX* leds) { + uint16_t age_frac = particle.age_frac16(particle.age); + CRGB c = particle.color_at(age_frac); - uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); - particle->draw_with_pen(leds, pos, c); + uint16_t pos = scale16(particle.position, leds->getLengthTotal() - 1); + particle.draw_with_pen(leds, pos, c); } -void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { +void drawRadius(Particle& particle, WS2812FX* leds, uint16_t pos, uint8_t radius, CRGB c, bool dim=true) { auto num_leds = leds->getLengthTotal(); for (int i = 0; i < radius; i++) { uint8_t bright = dim ? ((radius-i) * 255) / radius : 255; @@ -219,30 +218,30 @@ void drawRadius(Particle *particle, WS2812FX* leds, uint16_t pos, uint8_t radius uint8_t y = pos - i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(leds, y, c); + particle.draw_with_pen(leds, y, c); if (i == 0) continue; y = pos + i; if (y >= 0 && y < num_leds) - particle->draw_with_pen(leds, y, c); + particle.draw_with_pen(leds, y, c); } } -void drawPop(Particle *particle, WS2812FX* leds) { - uint16_t age_frac = particle->age_frac16(particle->age); - CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); +void drawPop(Particle& particle, WS2812FX* leds) { + uint16_t age_frac = particle.age_frac16(particle.age); + CRGB c = particle.color_at(age_frac); + uint16_t pos = scale16(particle.position, leds->getLengthTotal() - 1); uint8_t radius = scale16((sin16(age_frac/2) - 32768) * 2, 8); drawRadius(particle, leds, pos, radius, c); } -void drawBeatbox(Particle *particle, WS2812FX* leds) { - uint16_t age_frac = particle->age_frac16(particle->age); - CRGB c = particle->color_at(age_frac); - uint16_t pos = scale16(particle->position, leds->getLengthTotal() - 1); +void drawBeatbox(Particle& particle, WS2812FX* leds) { + uint16_t age_frac = particle.age_frac16(particle.age); + CRGB c = particle.color_at(age_frac); + uint16_t pos = scale16(particle.position, leds->getLengthTotal() - 1); uint8_t radius = 3; // Bump up the radius with any beats From 565e88335c41d8ca2c17b7d120e77cba15e0c10a Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 18 Aug 2024 10:20:25 -0700 Subject: [PATCH 236/263] Fix errors when strips have more LEDs than MAX_VIRTUAL_LEDs Remove excess LEDs buffering --- usermods/Tubes/controller.h | 36 ++++++++++++++++++-------- usermods/Tubes/led_strip.h | 23 +---------------- usermods/Tubes/pattern.h | 36 +++++++++++++------------- usermods/Tubes/virtual_strip.h | 46 +++++++++++++--------------------- 4 files changed, 62 insertions(+), 79 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 54378e60c9..a253dd5c3c 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -107,7 +107,6 @@ class PatternController : public MessageReceiver { const static int FRAMES_PER_SECOND = 60; // how often we animate, in frames per second const static int REFRESH_PERIOD = 1000 / FRAMES_PER_SECOND; // how often we animate, in milliseconds - uint8_t num_leds; VirtualStrip *vstrips[NUM_VSTRIPS]; uint8_t next_vstrip = 0; bool canOverride = false; @@ -146,18 +145,13 @@ class PatternController : public MessageReceiver { // When a pattern is boring, spice it up a bit with more effects bool isBoring = false; - PatternController(uint8_t num) : - num_leds(num), led_strip(num), node(this) { + PatternController() : node(this) { #ifdef USELCD lcd = new Lcd(); #endif for (auto i=0; i < NUM_VSTRIPS; i++) { -#ifdef DOUBLED - vstrips[i] = new VirtualStrip(num_leds * 2 + 1); -#else - vstrips[i] = new VirtualStrip(num_leds); -#endif + vstrips[i] = new VirtualStrip(); } } @@ -441,7 +435,7 @@ class PatternController : public MessageReceiver { if (fader < 255) { // Perform a cross-fade between current WLED mode and the external buffer for (int i = 0; i < length; i++) { - CRGB c = led_strip.getPixelColor(i); + CRGB c = getBlendedPixelColor(i); if (fader > 0) { CRGB color2 = strip.getPixelColor(i); uint8_t r = blend8(c.r, color2.r, fader); @@ -887,12 +881,34 @@ class PatternController : public MessageReceiver { wled_fader = vstrip->fader; vstrip->update(beat_frame, beat_pulse); - vstrip->blend(led_strip.leds, led_strip.num_leds, options.brightness, vstrip == first_strip); } effects.update(first_strip, beat_frame, (BeatPulse)beat_pulse); } + CRGB getBlendedPixelColor(int32_t pos) { + // Calculate the color of the pixel at position i by blending the colors of the virtual strips + CRGB color = CRGB::Black; + + for (uint8_t i=0; i < NUM_VSTRIPS; i++) { + VirtualStrip *vstrip = vstrips[i]; + + // Don't bother blending a fully faded strip, or the WLED strip itself + if (vstrip->fade == Dead || vstrip->isWled()) + continue; + + auto br = vstrip->brightness; //(options.brightness, vstrip->brightness); + + // Fetch the color from the strip and dim it according to the brightness + CRGB c = vstrip->getPixelColor(pos); + nscale8x3(c.r, c.g, c.b, br); + nscale8x3(c.r, c.g, c.b, vstrip->fader>>8); + color |= c; + } + + return color; + } + virtual void acknowledge() { addFlash(CRGB::Green); } diff --git a/usermods/Tubes/led_strip.h b/usermods/Tubes/led_strip.h index 80d461c15a..55c58591f4 100644 --- a/usermods/Tubes/led_strip.h +++ b/usermods/Tubes/led_strip.h @@ -3,34 +3,19 @@ #define USE_WLED #include "wled.h" -#define MAX_REAL_LEDS 150 - class LEDs { public: - CRGB leds[MAX_REAL_LEDS]; - // CRGB led_array[MAX_REAL_LEDS]; - const static int TARGET_FRAMES_PER_SECOND = 150; const static int TARGET_REFRESH = 1000 / TARGET_FRAMES_PER_SECOND; - int num_leds; - uint16_t fps = 0; - LEDs(int num=MAX_REAL_LEDS) : num_leds(num) { }; + LEDs() { }; void setup() { Serial.println((char *)F("LEDs: ok")); } - void reverse() { - for (int i=1; i<8; i++) { - CRGB c = leds[i]; - leds[i] = leds[16-i]; - leds[16-i] = c; - } - } - void update() { EVERY_N_MILLISECONDS( TARGET_REFRESH ) { fps++; @@ -43,10 +28,4 @@ class LEDs { fps = 0; } } - - CRGB getPixelColor(uint8_t pos) const { - if (pos > num_leds) - return CRGB::Black; - return leds[pos]; - } }; diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index a9cd57a3c5..94cec51dd0 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -6,14 +6,14 @@ void rainbow(VirtualStrip *strip) { // FastLED's built-in rainbow generator - fill_rainbow( strip->leds, strip->num_leds, strip->hue, 3); + fill_rainbow( strip->leds, strip->length(), strip->hue, 3); } void palette_wave(VirtualStrip *strip) { // FastLED's built-in rainbow generator uint8_t hue = strip->hue; - for (uint8_t i=0; i < strip->num_leds; i++) { + for (uint8_t i=0; i < strip->length(); i++) { CRGB c = strip->palette_color(i, hue); nscale8x3(c.r, c.g, c.b, sin8(hue*8)); strip->leds[i] = c; @@ -23,35 +23,35 @@ void palette_wave(VirtualStrip *strip) void particleTest(VirtualStrip *strip) { - fill_solid( strip->leds, strip->num_leds, CRGB::Black); + fill_solid( strip->leds, strip->length(), CRGB::Black); fill_solid( strip->leds, 2, strip->palette_color(0, strip->hue)); } void solidBlack(VirtualStrip *strip) { - fill_solid( strip->leds, strip->num_leds, CRGB::Black); + fill_solid( strip->leds, strip->length(), CRGB::Black); } void solidWhite(VirtualStrip *strip) { - fill_solid( strip->leds, strip->num_leds, CRGB::White); + fill_solid( strip->leds, strip->length(), CRGB::White); } void solidRed(VirtualStrip *strip) { - fill_solid( strip->leds, strip->num_leds, CRGB::Red); + fill_solid( strip->leds, strip->length(), CRGB::Red); } void solidBlue(VirtualStrip *strip) { - fill_solid( strip->leds, strip->num_leds, CRGB::Blue); + fill_solid( strip->leds, strip->length(), CRGB::Blue); } void confetti(VirtualStrip *strip) { strip->darken(2); - int pos = random16(strip->num_leds); + int pos = random16(strip->length()); strip->leds[pos] += strip->palette_color(random8(64), strip->hue); } @@ -65,8 +65,8 @@ void biwave(VirtualStrip *strip) uint16_t r = strip->frame * 32; r = cos16( r + random_offset ) + 32768; - uint8_t p1 = scaled16to8(l, 0, strip->num_leds-1); - uint8_t p2 = scaled16to8(r, 0, strip->num_leds-1); + uint8_t p1 = scaled16to8(l, 0, strip->length()-1); + uint8_t p2 = scaled16to8(r, 0, strip->length()-1); if (p2 < p1) { uint16_t t = p1; @@ -90,14 +90,14 @@ void sinelon(VirtualStrip *strip) // a colored dot sweeping back and forth, with fading trails strip->darken(30); - int pos = scale16(sin16( strip->frame << 5 ) + 32768, strip->num_leds-1); // beatsin16 re-implemented + int pos = scale16(sin16( strip->frame << 5 ) + 32768, strip->length()-1); // beatsin16 re-implemented strip->leds[pos] += strip->hue_color(); } void bpm_palette(VirtualStrip *strip) { uint8_t beat = strip->bpm_sin16(64, 255); - for (int i = 0; i < strip->num_leds; i++) { + for (int i = 0; i < strip->length(); i++) { CRGB c = strip->palette_color(i*2, strip->hue, beat-strip->hue+(i*10)); strip->leds[i] = c; } @@ -109,7 +109,7 @@ void bpm(VirtualStrip *strip) CRGBPalette16 palette = PartyColors_p; uint8_t beat = strip->bpm_sin16(64, 255); - for (int i = 0; i < strip->num_leds; i++) { + for (int i = 0; i < strip->length(); i++) { strip->leds[i] = ColorFromPalette(palette, strip->hue+(i*2), beat-strip->hue+(i*10)); } } @@ -123,7 +123,7 @@ void juggle(VirtualStrip *strip) for( int i = 0; i < 8; i++) { CRGB c = strip->palette_color(dothue + strip->hue); // c = CHSV(dothue, 200, 255); - strip->leds[beatsin16( i+7, 0, strip->num_leds-1 )] |= c; + strip->leds[beatsin16( i+7, 0, strip->length()-1 )] |= c; dothue += 32; } } @@ -142,18 +142,18 @@ void fillnoise8(uint32_t frame, uint8_t num_leds) { data = qsub8(data,16); data = qadd8(data,scale8(data,39)); - uint8_t olddata = noise[i]; + uint8_t olddata = noise[i % MAX_VIRTUAL_LEDS]; uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); - noise[i] = newdata; + noise[i % MAX_VIRTUAL_LEDS] = newdata; } } void drawNoise(VirtualStrip *strip) { // generate noise data - fillnoise8(strip->frame >> 2, strip->num_leds); + fillnoise8(strip->frame >> 2, strip->length()); - for(int i = 0; i < strip->num_leds; i++) { + for(int i = 0; i < strip->length(); i++) { CRGB color = strip->palette_color(noise[i], strip->hue); strip->leds[i] = color; } diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 2c8fa72e8e..74731047b1 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -7,7 +7,7 @@ #include "wled.h" #define DEFAULT_FADE_SPEED 100 -#define MAX_VIRTUAL_LEDS 150 +#define MAX_VIRTUAL_LEDS 500 #define DEFAULT_WLED_FX FX_MODE_RAINBOW_CYCLE @@ -45,7 +45,6 @@ class VirtualStrip { public: CRGB leds[MAX_VIRTUAL_LEDS]; - uint8_t num_leds; uint8_t brightness; // Fade in/out @@ -62,11 +61,19 @@ class VirtualStrip { bool beat_pulse; int bps = 0; - VirtualStrip(uint8_t num) : num_leds(num) + VirtualStrip() { fade = Dead; } + int32_t length() { + // Try to be the same as the main segment, but not if it's too big + auto len = strip.getMainSegment().length(); + if (len > MAX_VIRTUAL_LEDS) + return MAX_VIRTUAL_LEDS; + return len; + } + void load(Background &b, uint8_t fs=DEFAULT_FADE_SPEED) { background = b; @@ -90,12 +97,12 @@ class VirtualStrip { void darken(uint8_t amount=10) { - fadeToBlackBy( leds, num_leds, amount); + fadeToBlackBy( leds, length(), amount); } void fill(CRGB crgb) { - fill_solid( leds, num_leds, crgb); + fill_solid( leds, length(), crgb); } void update(BeatFrame_24_8 fr, uint8_t bp) @@ -171,30 +178,7 @@ class VirtualStrip { CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { return CHSV(hue + offset, saturation, value); } - - void blend(CRGB output[], uint8_t num_leds, uint8_t br, bool overwrite=0) { - if (fade == Dead) - return; - - // WLED is blended in elsewhere - if (isWled()) - return; - - br = scale8(brightness, br); - - for (unsigned i=0; i < num_leds; i++) { - uint8_t pos = i; - CRGB c = leds[pos]; - - nscale8x3(c.r, c.g, c.b, brightness); - nscale8x3(c.r, c.g, c.b, fader>>8); - if (overwrite) - output[i] = c; - else - output[i] |= c; - } - } - + uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) { return scaled16to8(sin16( frame << 7 ) + 32768, lowest, highest); @@ -205,4 +189,8 @@ class VirtualStrip { return scaled16to8(cos16( frame << 7 ) + 32768, lowest, highest); } + CRGB getPixelColor(int32_t pos) { + return leds[pos % length()]; + } + }; From 557afa5fb4ee4ce92b061313474294ef128c7426 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 18 Aug 2024 14:11:51 -0700 Subject: [PATCH 237/263] PR suggestions --- usermods/Tubes/Tubes.h | 7 +++++++ usermods/Tubes/controller.h | 7 ++++++- usermods/Tubes/pattern.h | 11 ++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/usermods/Tubes/Tubes.h b/usermods/Tubes/Tubes.h index 1b4b4214be..ebd34b6c4e 100644 --- a/usermods/Tubes/Tubes.h +++ b/usermods/Tubes/Tubes.h @@ -20,6 +20,13 @@ #define MASTER_PIN 25 #define LEGACY_PIN 32 // DigUno Q4 +#ifndef NUM_STRIPS +#define NUM_STRIPS 1 +#endif + +#ifndef MAX_REAL_LEDS +#define MAX_REAL_LEDS (DEFAULT_LED_COUNT * NUM_STRIPS) +#endif class TubesUsermod : public Usermod { private: diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index a253dd5c3c..ccb5c0b5a3 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -155,6 +155,11 @@ class PatternController : public MessageReceiver { } } + // Compatibility ctor for older call sites that pass a real LED count. + PatternController(uint16_t real_led_count) : PatternController() { + (void)real_led_count; + } + bool isMasterRole() const { #if defined(GOLDEN) || defined(CHRISTMAS) return true; @@ -1444,4 +1449,4 @@ class PatternController : public MessageReceiver { } -}; \ No newline at end of file +}; diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index 94cec51dd0..be940c87d9 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -133,7 +133,12 @@ uint8_t noise[MAX_VIRTUAL_LEDS]; void fillnoise8(uint32_t frame, uint8_t num_leds) { uint16_t scale = 17; uint8_t dataSmoothing = 240; - + + auto max_leds = sizeof(noise)/sizeof(noise[0]); + if (num_leds > max_leds) { + num_leds = max_leds; + } + for (int i = 0; i < num_leds; i++) { uint8_t data = inoise8(i * scale, frame>>2); @@ -142,9 +147,9 @@ void fillnoise8(uint32_t frame, uint8_t num_leds) { data = qsub8(data,16); data = qadd8(data,scale8(data,39)); - uint8_t olddata = noise[i % MAX_VIRTUAL_LEDS]; + uint8_t olddata = noise[i]; uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); - noise[i % MAX_VIRTUAL_LEDS] = newdata; + noise[i] = newdata; } } From b6ae6bb8a7f66238833d74f1b35baa33f1cf7cc7 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 18 Aug 2024 14:31:20 -0700 Subject: [PATCH 238/263] Update explanation of strip blending --- usermods/Tubes/controller.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index ccb5c0b5a3..c2e0f0ac98 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -895,6 +895,7 @@ class PatternController : public MessageReceiver { // Calculate the color of the pixel at position i by blending the colors of the virtual strips CRGB color = CRGB::Black; + bool first_strip = true; for (uint8_t i=0; i < NUM_VSTRIPS; i++) { VirtualStrip *vstrip = vstrips[i]; @@ -902,13 +903,22 @@ class PatternController : public MessageReceiver { if (vstrip->fade == Dead || vstrip->isWled()) continue; - auto br = vstrip->brightness; //(options.brightness, vstrip->brightness); + auto br = vstrip->brightness; + // TODO: code intended to use scale8(options.brightness, vstrip->brightness); + // but that was never implemented - should review later to see if we want + // options.brightness to be a factor in the brightness of the strip // Fetch the color from the strip and dim it according to the brightness CRGB c = vstrip->getPixelColor(pos); nscale8x3(c.r, c.g, c.b, br); nscale8x3(c.r, c.g, c.b, vstrip->fader>>8); - color |= c; + + if (first_strip) { + color = c; + first_strip = false; + } else { + color |= c; + } } return color; From 6e9ce673419eead18bdabb015b1bea28e3ba7516 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Sun, 18 Aug 2024 17:34:57 -0700 Subject: [PATCH 239/263] add support for espnow message filtering and make a few more methods const for clarity --- usermods/Tubes/bluetooth.h | 4 ++- usermods/Tubes/controller.h | 4 +-- usermods/Tubes/node.h | 53 ++++++++++++++----------------------- wled00/espnow_broadcast.cpp | 23 +++++++++++++--- wled00/espnow_broadcast.h | 21 ++++++++++++--- 5 files changed, 62 insertions(+), 43 deletions(-) diff --git a/usermods/Tubes/bluetooth.h b/usermods/Tubes/bluetooth.h index d98f5bcf88..3a48a51861 100644 --- a/usermods/Tubes/bluetooth.h +++ b/usermods/Tubes/bluetooth.h @@ -1,3 +1,4 @@ +#if 0 #pragma once // THIS FILE ISN'T USED ANY MORE @@ -492,4 +493,5 @@ void procUpdaterTask(void* pvParameters) { pClient->disconnect(); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index c2e0f0ac98..d760d11b66 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1186,7 +1186,7 @@ class PatternController : public MessageReceiver { node.sendCommand(COMMAND_BEATS, &bpm, sizeof(bpm)); } - virtual bool onCommand(CommandId command, void *data) { + virtual bool onCommand(CommandId command, void *data) override { switch (command) { case COMMAND_INFO: Serial.printf(" \"%s\"\n", @@ -1343,7 +1343,7 @@ class PatternController : public MessageReceiver { force_next(); } - virtual bool onButton(uint8_t button_id) { + virtual bool onButton(uint8_t button_id) override { bool isMaster = !this->node.isFollowing(); switch (button_id) { diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 82799f678d..2c44f4861d 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -64,14 +64,8 @@ const char *command_name(CommandId command) { class MessageReceiver { public: - virtual bool onCommand(CommandId command, void *data) { - // Abstract: subclasses must define - return false; - } - virtual bool onButton(uint8_t button_id) { - // Abstract: subclasses must define - return false; - } + virtual bool onCommand(CommandId command, void *data) = 0; + virtual bool onButton(uint8_t button_id) = 0; }; class LightNode { @@ -174,7 +168,7 @@ class LightNode { } } - void printMessage(const NodeMessage* message, signed int rssi) { + void printMessage(const NodeMessage* message, signed int rssi) const { Serial.printf("%03X/%03X %s", message->header.id, message->header.uplinkId, @@ -187,20 +181,6 @@ class LightNode { } void onPeerData(const uint8_t* address, const NodeMessage* message, uint8_t len, signed int rssi, bool broadcast) { - // Ignore this message if it isn't a valid message payload. - if (len != sizeof(*message)) - return; - - // Ignore this message if it's the wrong version. - if (message->header.version != header.version) { -#ifdef NODE_DEBUGGING - Serial.print(" -- !version "); - printMessage(message, rssi); - Serial.println(); -#endif - return; - } - // Track that another node exists, updating this node's understanding of the mesh. onPeerPing(message->header); @@ -350,6 +330,7 @@ class LightNode { delay(2000); #endif + espnowBroadcast.registerFilter(onEspNowFilter); espnowBroadcast.registerCallback(onEspNowMessage); Serial.println("setup: ok"); @@ -358,7 +339,7 @@ class LightNode { void update() { //process any wifi events to turn on/off ESPNode - checkESPNowState(); + updateESPNowState(); // Check the last time we heard from the uplink node if (isFollowing() && uplinkTimer.ended()) { @@ -400,19 +381,19 @@ class LightNode { onMeshChange(); } - bool isFollowing() { + bool isFollowing() const { return header.uplinkId != 0; } protected: - void checkESPNowState() { + void updateESPNowState() { auto state = espnowBroadcast.getState(); static auto prev = espnowBroadcast.STOPPED; switch(state) { case ESPNOWBroadcast::STOPPED: if (NODE_STATUS_QUIET != status) { - Serial.printf("checkESPNowState() - %d node_status:%s\n", state, status_code()); + Serial.printf("updateESPNowState() - %d node_status:%s\n", state, status_code()); status = NODE_STATUS_QUIET; rebroadcastTimer.stop(); Serial.printf("LightNode %s\n", status_code()); @@ -420,12 +401,12 @@ class LightNode { break; case ESPNOWBroadcast::STARTING: {} if ( state != prev ) { - Serial.printf("checkESPNowState() - %d node_status:%s\n", state, status_code()); + Serial.printf("updateESPNowState() - %d node_status:%s\n", state, status_code()); } break; case ESPNOWBroadcast::STARTED: if (NODE_STATUS_QUIET == status) { - Serial.printf("checkESPNowState() - %d node_status:%s\n", state, status_code()); + Serial.printf("updateESPNowState() - %d node_status:%s\n", state, status_code()); status = NODE_STATUS_RECEIVING; statusTimer.start(STATUS_TIMEOUT_BASE - header.id / 2); Serial.printf("LightNode %s\n", status_code()); @@ -452,10 +433,6 @@ class LightNode { } wizmote_message; void onWizmote(const uint8_t* address, const wizmote_message* data, uint8_t len) { - // First make sure this is a WizMote message. - if (len != sizeof(wizmote_message) || data->byte8 != 1 || data->byte9 != 100 || data->byte5 != 32) - return; - static uint32_t last_seq = 0; uint32_t cur_seq = data->seq[0] | (data->seq[1] << 8) | (data->seq[2] << 16) | (data->seq[3] << 24); if (cur_seq == last_seq) @@ -466,6 +443,7 @@ class LightNode { } static void onEspNowMessage(const uint8_t *address, const uint8_t *msg, uint8_t len, int8_t rssi) { + // basic length and field checking has been done in onEspNowFilter if (msg) { if(len == sizeof(NodeMessage)) { instance->onPeerData(address, (const NodeMessage*)msg, len, rssi, true); @@ -479,6 +457,15 @@ class LightNode { } } + static bool onEspNowFilter(const uint8_t *address, const uint8_t *msg, uint8_t len, int8_t rssi) { + if (len == sizeof(NodeMessage)) { + return ((const NodeMessage*)msg)->header.version == instance->header.version; + } else if (len == sizeof(wizmote_message)) { + auto wizmote = (const wizmote_message*)msg; + return !( wizmote->byte8 != 1 || wizmote->byte9 != 100 || wizmote->byte5 != 32); + } + return false; + } }; LightNode* LightNode::instance = nullptr; diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 946745e16e..fe0b0c5427 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -33,7 +33,7 @@ ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); #endif //#define ESPNOW_DEBUGGING -//#define ESNOW_CALLBACK_DEBUGGING // Serial is called from multiple threads +//#define ESPNOW_CALLBACK_DEBUGGING // Serial is called from multiple threads #define BROADCAST_ADDR_ARRAY_INITIALIZER {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define WLED_ESPNOW_WIFI_CHANNEL 1 @@ -57,7 +57,7 @@ class ESPNOWBroadcastImpl : public ESPNOWBroadcast { friend ESPNOWBroadcast; std::atomic _state {STOPPED}; - STATE getState() { + STATE getState() const { return _state.load(); } @@ -71,6 +71,8 @@ class ESPNOWBroadcastImpl : public ESPNOWBroadcast { static void onESPNowRxCallback(const uint8_t *mac_addr, const uint8_t *data, int len); + receive_filter_t _rxFilter = nullptr; + class QueuedNetworkRingBuffer { protected: //QueuedNetworkMessage messages[WLED_ESPNOW_MAX_QUEUED_MESSAGES]; @@ -103,7 +105,7 @@ ESPNOWBroadcastImpl espnowBroadcastImpl {}; ESPNOWBroadcast espnowBroadcast {}; -ESPNOWBroadcast::STATE ESPNOWBroadcast::getState() { +ESPNOWBroadcast::STATE ESPNOWBroadcast::getState() const { #ifdef ESP32 return espnowBroadcastImpl.getState(); #else @@ -251,6 +253,13 @@ bool ESPNOWBroadcast::removeCallback( ESPNOWBroadcast::receive_callback_t callba } +ESPNOWBroadcast::receive_filter_t ESPNOWBroadcast::registerFilter( ESPNOWBroadcast::receive_filter_t filter ) { + auto old = espnowBroadcastImpl._rxFilter; + espnowBroadcastImpl._rxFilter = filter; + return old; +} + + #ifdef ESP32 void ESPNOWBroadcastImpl::start() { @@ -398,7 +407,13 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * rssi = 0; } - if (!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { + if (espnowBroadcastImpl._rxFilter) { + if (!espnowBroadcastImpl._rxFilter(mac, data, len, rssi)) { + return; + } + } + + if(!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { Serial.printf("Failed to queue message (%d bytes) to ring buffer. Dropping message\n", len); } else { #ifdef ESPNOW_CALLBACK_DEBUGGING diff --git a/wled00/espnow_broadcast.h b/wled00/espnow_broadcast.h index ae23b9ee3c..d14786065f 100644 --- a/wled00/espnow_broadcast.h +++ b/wled00/espnow_broadcast.h @@ -3,7 +3,7 @@ #ifndef WLED_DISABLE_ESPNOW_NEW -#include "const.h" +//#include "const.h" #ifndef WLED_ESPNOW_MAX_QUEUED_MESSAGES #define WLED_ESPNOW_MAX_QUEUED_MESSAGES 6 @@ -14,6 +14,9 @@ #endif #ifndef WLED_ESPNOW_MAX_REGISTERED_CALLBACKS + #ifndef WLED_MAX_USERMODS + #define WLED_MAX_USERMODS 1 + #endif #define WLED_ESPNOW_MAX_REGISTERED_CALLBACKS WLED_MAX_USERMODS+1 #endif @@ -31,6 +34,15 @@ class ESPNOWBroadcast { bool registerCallback( receive_callback_t callback ); bool removeCallback( receive_callback_t callback ); + // the receive filter happens from the WiFi task and this should not do any lengthly processing + // additionally thread synchronization may need to be done with the main application task thread + // only a single filter may be register at a given time + // The filter should return false if the received network message should be ignored and not + // processed by future callbacks + typedef bool (*receive_filter_t)(const uint8_t *sender, const uint8_t *data, uint8_t len, int8_t rssi); + receive_filter_t registerFilter( receive_filter_t filter = nullptr ); + + enum STATE { STOPPED = 0, STARTING, @@ -38,12 +50,15 @@ class ESPNOWBroadcast { MAX }; - STATE getState(); + STATE getState() const; + + bool isStarted() const { + return STATE::STARTED == getState(); + } protected: receive_callback_t _rxCallbacks[WLED_ESPNOW_MAX_REGISTERED_CALLBACKS] = {0}; static constexpr size_t _rxCallbacksSize = sizeof(_rxCallbacks)/sizeof(_rxCallbacks[0]); - }; extern ESPNOWBroadcast espnowBroadcast; From 594a08cb3be38fc9f64745270fe9e67c4c5a4a72 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Sun, 18 Aug 2024 17:53:04 -0700 Subject: [PATCH 240/263] fix esp8266 build --- wled00/espnow_broadcast.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index fe0b0c5427..818e2725c9 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -254,9 +254,13 @@ bool ESPNOWBroadcast::removeCallback( ESPNOWBroadcast::receive_callback_t callba } ESPNOWBroadcast::receive_filter_t ESPNOWBroadcast::registerFilter( ESPNOWBroadcast::receive_filter_t filter ) { +#ifdef ESP32 auto old = espnowBroadcastImpl._rxFilter; espnowBroadcastImpl._rxFilter = filter; return old; +#else + return nullptr; +#endif } From 6d7a769e9a9c6afada134e8eb1dc6dca79b989d5 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Sun, 18 Aug 2024 22:32:31 -0700 Subject: [PATCH 241/263] add debug counters to espnow --- wled00/espnow_broadcast.cpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 818e2725c9..f6cfc1f397 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -34,6 +34,7 @@ ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); //#define ESPNOW_DEBUGGING //#define ESPNOW_CALLBACK_DEBUGGING // Serial is called from multiple threads +#define ESPNOW_DEBUG_COUNTERS #define BROADCAST_ADDR_ARRAY_INITIALIZER {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define WLED_ESPNOW_WIFI_CHANNEL 1 @@ -73,6 +74,12 @@ class ESPNOWBroadcastImpl : public ESPNOWBroadcast { receive_filter_t _rxFilter = nullptr; +#ifdef ESPNOW_DEBUG_COUNTERS + std::atomic _received {0}; + std::atomic _processed {0}; + std::atomic _loop {0}; +#endif + class QueuedNetworkRingBuffer { protected: //QueuedNetworkMessage messages[WLED_ESPNOW_MAX_QUEUED_MESSAGES]; @@ -182,16 +189,21 @@ bool ESPNOWBroadcastImpl::setupWiFi() { void ESPNOWBroadcast::loop(size_t maxMessagesToProcess /*= 1*/) { #ifdef ESP32 +#ifdef ESPNOW_DEBUG_COUNTERS + espnowBroadcastImpl._loop++; +#endif switch (espnowBroadcastImpl._state.load()) { case ESPNOWBroadcast::STARTING: // if WiFI is in starting state, actually stat ESPNow from our main task thread. espnowBroadcastImpl.start(); break; case ESPNOWBroadcast::STARTED: { - auto ndx = maxMessagesToProcess; - while(ndx-- > 0) { + while(maxMessagesToProcess-- > 0) { auto *msg = espnowBroadcastImpl.queuedNetworkRingBuffer.pop(); if (msg) { +#ifdef ESPNOW_DEBUG_COUNTERS + espnowBroadcastImpl._processed++; +#endif auto callback = _rxCallbacks; while( *callback ) { (*callback)(msg->mac, msg->data, msg->len, msg->rssi); @@ -229,6 +241,10 @@ bool ESPNOWBroadcast::registerCallback( ESPNOWBroadcast::receive_callback_t call // last element is always null size_t ndx; for (ndx = 0; ndx < _rxCallbacksSize-1; ndx++) { + // already registered + if (callback == _rxCallbacks[ndx]) { + break; + } if (nullptr == _rxCallbacks[ndx]) { _rxCallbacks[ndx] = callback; break; @@ -417,8 +433,22 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * } } +#ifdef ESPNOW_DEBUG_COUNTERS + espnowBroadcastImpl._received++; +#endif if(!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { - Serial.printf("Failed to queue message (%d bytes) to ring buffer. Dropping message\n", len); + Serial.printf("Failed to queue message (%d bytes) to ring buffer. Dropping message\n" +#ifdef ESPNOW_DEBUG_COUNTERS + "\tState: %d\t loop:0x%x\t recv:0x%x\t processed:0x%x\n" +#endif + , len +#ifdef ESPNOW_DEBUG_COUNTERS + , espnowBroadcastImpl.getState(), + espnowBroadcastImpl._loop.load(), + espnowBroadcastImpl._received.load(), + espnowBroadcastImpl._processed.load() +#endif + ); } else { #ifdef ESPNOW_CALLBACK_DEBUGGING char buf[128]; From 30b4b236104eabd97c6145cee8a6a0c68def4f7e Mon Sep 17 00:00:00 2001 From: Craig Link Date: Sun, 18 Aug 2024 22:16:09 -0700 Subject: [PATCH 242/263] re-enable serial input accidently disabled by merge - https://github.com/Aircoookie/WLED/commit/d03ad1f01c6b428dc3eb5674a8aa1dbf2bc7cdeb also current version of Espressif Ardunio SDK seems to break it as well --- platformio_tubes.ini | 4 +++- wled00/wled.cpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 75d8d20f6b..629f5fa374 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -7,7 +7,8 @@ ; For devices with those inputs use the [tubes] settings ; which adds those back in platform = espressif32@6.8.1 -platform_packages = ;framework-arduinoespressif32 @ 3.20017.0 +platform_packages = framework-arduinoespressif32 @ 3.20014.231204 +;platform_packages = ;framework-arduinoespressif32 @ 3.20017.0 build_unflags = -D LOROL_LITTLEFS -D CONFIG_ASYNC_TCP_USE_WDT @@ -30,6 +31,7 @@ build_flags = -D WLED_DISABLE_WEBSOCKETS -D WLED_DISABLE_ADALIGHT -D WLED_DISABLE_ESPNOW + -D WLED_DISABLE_SERIAL -D IRTYPE=0 lib_ignore = ESPAsyncTCP diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 5c64aa3b46..657339b11c 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -58,7 +58,9 @@ void WLED::loop() #ifndef WLED_DISABLE_ESPNOW handleRemote(); #endif + #ifndef WLED_DISABLE_SERIAL handleSerial(); + #endif handleImprovWifiScan(); handleNotifications(); handleTransitions(); From 00dc3382b6deeaa566502e2841d42d3e9dcea024 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Sun, 18 Aug 2024 23:38:17 -0700 Subject: [PATCH 243/263] add const to appropriate read-only functions --- usermods/Tubes/beats.h | 4 ++-- usermods/Tubes/controller.h | 8 +++---- usermods/Tubes/global_state.h | 40 +++++++++++++--------------------- usermods/Tubes/particle.h | 6 ++--- usermods/Tubes/virtual_strip.h | 14 ++++++------ 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/usermods/Tubes/beats.h b/usermods/Tubes/beats.h index d540ef46b5..617872e910 100644 --- a/usermods/Tubes/beats.h +++ b/usermods/Tubes/beats.h @@ -1,5 +1,5 @@ #pragma once - +#include "wled.h" #include "timer.h" #define DEFAULT_BPM 120 @@ -60,7 +60,7 @@ class BeatController { accum = 0; } - void print_bpm() { + void print_bpm() const { Serial.print(bpm >> 8); uint8_t frac = scale8(100, bpm & 0xFF); Serial.print(F(".")); diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index d760d11b66..8211a05029 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -592,7 +592,7 @@ class PatternController : public MessageReceiver { background_changed(); } - bool isShowingWled() { + bool isShowingWled() const { return current_state.pattern_id >= numInternalPatterns; } @@ -756,7 +756,7 @@ class PatternController : public MessageReceiver { set_wled_palette(background.palette_id); } - bool isUnderWledControl() { + bool isUnderWledControl() const { return paletteOverride || patternOverride; } @@ -891,7 +891,7 @@ class PatternController : public MessageReceiver { effects.update(first_strip, beat_frame, (BeatPulse)beat_pulse); } - CRGB getBlendedPixelColor(int32_t pos) { + CRGB getBlendedPixelColor(int32_t pos) const { // Calculate the color of the pixel at position i by blending the colors of the virtual strips CRGB color = CRGB::Black; @@ -947,7 +947,7 @@ class PatternController : public MessageReceiver { } } - accum88 parse_number(char *s) { + accum88 parse_number(char *s) const { uint16_t n=0, d=0; while (*s == ' ') diff --git a/usermods/Tubes/global_state.h b/usermods/Tubes/global_state.h index 1677b74de9..5042d41d62 100644 --- a/usermods/Tubes/global_state.h +++ b/usermods/Tubes/global_state.h @@ -23,32 +23,22 @@ class TubeState { void print() { uint16_t phrase = beat_frame >> 12; - Serial.print(F("[")); - Serial.print(phrase); - Serial.print(F(".")); - Serial.print((beat_frame >> 8) % 16); - Serial.print(F(" P")); - Serial.print(pattern_id); - Serial.print(F(",")); - Serial.print(pattern_sync_id); - Serial.print(F(" C")); - Serial.print(palette_id); - Serial.print(F(" E")); - Serial.print(effect_params.effect); - Serial.print(F(",")); - Serial.print(effect_params.pen); - Serial.print(F(",")); - Serial.print(effect_params.beat); - Serial.print(F(",")); - Serial.print(effect_params.chance); - Serial.print(F(" ")); - Serial.print(bpm >> 8); uint8_t frac = scale8(100, bpm & 0xFF); - Serial.print(F(".")); - if (frac < 10) - Serial.print(F("0")); - Serial.print(frac); - Serial.print(F("bpm]")); + char buf[128]; + sprintf(buf, "[%d.%d P%d,%d C%d E%d,%d,%d,%d %d.%02dbpm]", + phrase, + (beat_frame >> 8) % 16, + pattern_id, + pattern_sync_id, + palette_id, + effect_params.effect, + effect_params.pen, + effect_params.beat, + effect_params.chance, + bpm >> 8, + frac + ); + Serial.print(buf); } }; diff --git a/usermods/Tubes/particle.h b/usermods/Tubes/particle.h index db4f773f83..01e1399a04 100644 --- a/usermods/Tubes/particle.h +++ b/usermods/Tubes/particle.h @@ -60,7 +60,7 @@ class Particle { velocity = delta16(velocity, gravity); } - uint16_t age_frac16(BeatFrame_24_8 age) + uint16_t age_frac16(BeatFrame_24_8 age) const { if (age >= lifetime) return 65535; @@ -68,7 +68,7 @@ class Particle { return a / lifetime; } - uint16_t udelta16(uint16_t x, int16_t dx) + uint16_t udelta16(uint16_t x, int16_t dx) const { if (dx > 0 && 65535-x < dx) return 65335; @@ -77,7 +77,7 @@ class Particle { return x + dx; } - int16_t delta16(int16_t x, int16_t dx) + int16_t delta16(int16_t x, int16_t dx) const { if (dx > 0 && 32767-x < dx) return 32767; diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 74731047b1..032599053f 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -66,7 +66,7 @@ class VirtualStrip { fade = Dead; } - int32_t length() { + int32_t length() const { // Try to be the same as the main segment, but not if it's too big auto len = strip.getMainSegment().length(); if (len > MAX_VIRTUAL_LEDS) @@ -83,7 +83,7 @@ class VirtualStrip { brightness = DEF_BRIGHT; } - bool isWled() { + bool isWled() const { return background.wled_fx_id != 0; } @@ -169,27 +169,27 @@ class VirtualStrip { } } - CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) { + CRGB palette_color(uint8_t c, uint8_t offset=0, uint8_t brightness=255) const { Segment& segment = strip.getMainSegment(); uint32_t color = segment.color_from_palette(c + offset, false, true, 255, brightness); return CRGB(color); } - CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) { + CRGB hue_color(uint8_t offset=0, uint8_t saturation=255, uint8_t value=192) const { return CHSV(hue + offset, saturation, value); } - uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) + uint8_t bpm_sin16( uint16_t lowest=0, uint16_t highest=65535 ) const { return scaled16to8(sin16( frame << 7 ) + 32768, lowest, highest); } - uint8_t bpm_cos16( uint16_t lowest=0, uint16_t highest=65535 ) + uint8_t bpm_cos16( uint16_t lowest=0, uint16_t highest=65535 ) const { return scaled16to8(cos16( frame << 7 ) + 32768, lowest, highest); } - CRGB getPixelColor(int32_t pos) { + CRGB getPixelColor(int32_t pos) const { return leds[pos % length()]; } From 3cf9edf591709a15512a4ce171bfbfa41693115f Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 19 Aug 2024 08:21:49 -0700 Subject: [PATCH 244/263] add exit counter for espnow loop --- wled00/espnow_broadcast.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index f6cfc1f397..0e0f6cd2a9 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -78,6 +78,7 @@ class ESPNOWBroadcastImpl : public ESPNOWBroadcast { std::atomic _received {0}; std::atomic _processed {0}; std::atomic _loop {0}; + std::atomic _exit {0}; #endif class QueuedNetworkRingBuffer { @@ -219,6 +220,9 @@ void ESPNOWBroadcast::loop(size_t maxMessagesToProcess /*= 1*/) { default: break; } +#ifdef ESPNOW_DEBUG_COUNTERS + espnowBroadcastImpl._exit++; +#endif #endif // ESP32 } @@ -439,12 +443,13 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * if(!espnowBroadcastImpl.queuedNetworkRingBuffer.push(mac, data, len, rssi)) { Serial.printf("Failed to queue message (%d bytes) to ring buffer. Dropping message\n" #ifdef ESPNOW_DEBUG_COUNTERS - "\tState: %d\t loop:0x%x\t recv:0x%x\t processed:0x%x\n" + "\tState: %d\t loop:0x%x\t exit:0x%x recv:0x%x\t processed:0x%x\n" #endif , len #ifdef ESPNOW_DEBUG_COUNTERS , espnowBroadcastImpl.getState(), espnowBroadcastImpl._loop.load(), + espnowBroadcastImpl._exit.load(), espnowBroadcastImpl._received.load(), espnowBroadcastImpl._processed.load() #endif From f293d540dda60f4d682104130f0916b44efc106a Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 19 Aug 2024 08:58:04 -0700 Subject: [PATCH 245/263] add yield to espnow loop for good measure --- usermods/Tubes/debug.h | 8 +++++++- wled00/espnow_broadcast.cpp | 9 +++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index afdb0bc910..3e9cf19f79 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -93,13 +93,19 @@ class DebugController { } Serial.println(); - Serial.printf("=== firmware: v%d from SSID %s %u.%u.%u.%u OTA=%d\n\n", + Serial.printf("=== firmware: v%d " +#ifdef TUBES_AUTOUPDATER + "from SSID %s %u.%u.%u.%u " +#endif + "OTA=%d\n\n", controller.updater.current_version.version, +#ifdef TUBES_AUTOUPDATER controller.updater.current_version.ssid, controller.updater.current_version.host[0], controller.updater.current_version.ssid[1], controller.updater.current_version.ssid[2], controller.updater.current_version.ssid[3], +#endif controller.updater.status ); diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 0e0f6cd2a9..984d0ddca7 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -205,15 +205,16 @@ void ESPNOWBroadcast::loop(size_t maxMessagesToProcess /*= 1*/) { #ifdef ESPNOW_DEBUG_COUNTERS espnowBroadcastImpl._processed++; #endif - auto callback = _rxCallbacks; - while( *callback ) { - (*callback)(msg->mac, msg->data, msg->len, msg->rssi); - callback++; + for( auto ndx = 0; ndx < (sizeof(_rxCallbacks)/sizeof(_rxCallbacks[0]))-1; ndx++) { + if(_rxCallbacks[ndx]) { + _rxCallbacks[0](msg->mac, msg->data, msg->len, msg->rssi); + } } espnowBroadcastImpl.queuedNetworkRingBuffer.popComplete(msg); } else { break; } + yield(); } break; } From 473115bc3d04b49af401019e2fdca172d0d1ead3 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 19 Aug 2024 09:00:00 -0700 Subject: [PATCH 246/263] add yield to espnow loop for good measure --- wled00/espnow_broadcast.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 984d0ddca7..6700bb5672 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -207,7 +207,7 @@ void ESPNOWBroadcast::loop(size_t maxMessagesToProcess /*= 1*/) { #endif for( auto ndx = 0; ndx < (sizeof(_rxCallbacks)/sizeof(_rxCallbacks[0]))-1; ndx++) { if(_rxCallbacks[ndx]) { - _rxCallbacks[0](msg->mac, msg->data, msg->len, msg->rssi); + _rxCallbacks[ndx](msg->mac, msg->data, msg->len, msg->rssi); } } espnowBroadcastImpl.queuedNetworkRingBuffer.popComplete(msg); From 00b20b2f2d9ada8d337c841f6a3335eea66f1bcf Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 19 Aug 2024 09:57:31 -0700 Subject: [PATCH 247/263] Remove infinite loops from pattern generators; add some more debugging --- usermods/Tubes/debug.h | 6 +++++ usermods/Tubes/node.h | 16 +++++++++---- usermods/Tubes/pattern.h | 42 +++++++++++++++++----------------- usermods/Tubes/virtual_strip.h | 23 ++++++++++--------- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index 3e9cf19f79..ba5d234801 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -64,6 +64,12 @@ class DebugController { if (controller.sound.active) { Serial.print("SOUND "); } + if (controller.node.isLeading()) { + Serial.print("LEADING "); + } + if (controller.node.isFollowing()) { + Serial.print("FOLLLOWING "); + } Serial.printf("role=%d power_save=%d\n", controller.role, controller.power_save diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index 2c44f4861d..c22b3af92c 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -161,9 +161,10 @@ class LightNode { // If a message indicates that another node is following this one, or // should be (it's not following anything, but this node's ID is higher) // enter or continue re-broadcasting mode. - if (node.uplinkId == header.id - || (node.uplinkId == 0 && node.id < header.id)) { - Serial.printf(" %03X/%03X is following me", node.id, node.uplinkId); + if (node.uplinkId == header.id || (node.uplinkId == 0 && node.id < header.id)) { + if (!isLeading()) { + Serial.printf(" LEADING because %03X/%03X is following me\n", node.id, node.uplinkId); + } rebroadcastTimer.start(REBROADCAST_TIME); } } @@ -240,7 +241,7 @@ class LightNode { } // Re-broadcast the message if appropriate - if (!rebroadcastTimer.ended() && message->recipients != RECIPIENTS_INFO) { + if (isLeading() && message->recipients != RECIPIENTS_INFO) { static NodeMessage msg; memcpy(&msg, &message, len); msg.header = header; @@ -345,7 +346,6 @@ class LightNode { if (isFollowing() && uplinkTimer.ended()) { follow(NULL); } - } void reset(MeshId id = 0) { @@ -384,6 +384,12 @@ class LightNode { bool isFollowing() const { return header.uplinkId != 0; } + bool isLeading() const { + // For now, leading mode is defined as being in re-broadcast mode for any reason. + // Any node that thinks it has the highest ID is leading, but so are any nodes that + // have seen another node that should be following the leader it is following. + return !rebroadcastTimer.ended(); + } protected: diff --git a/usermods/Tubes/pattern.h b/usermods/Tubes/pattern.h index be940c87d9..0bc32f35a8 100644 --- a/usermods/Tubes/pattern.h +++ b/usermods/Tubes/pattern.h @@ -6,14 +6,14 @@ void rainbow(VirtualStrip *strip) { // FastLED's built-in rainbow generator - fill_rainbow( strip->leds, strip->length(), strip->hue, 3); + fill_rainbow( strip->leds, strip->num_leds, strip->hue, 3); } void palette_wave(VirtualStrip *strip) { // FastLED's built-in rainbow generator uint8_t hue = strip->hue; - for (uint8_t i=0; i < strip->length(); i++) { + for (auto i=0; i < strip->num_leds; i++) { CRGB c = strip->palette_color(i, hue); nscale8x3(c.r, c.g, c.b, sin8(hue*8)); strip->leds[i] = c; @@ -23,35 +23,35 @@ void palette_wave(VirtualStrip *strip) void particleTest(VirtualStrip *strip) { - fill_solid( strip->leds, strip->length(), CRGB::Black); + fill_solid( strip->leds, strip->num_leds, CRGB::Black); fill_solid( strip->leds, 2, strip->palette_color(0, strip->hue)); } void solidBlack(VirtualStrip *strip) { - fill_solid( strip->leds, strip->length(), CRGB::Black); + strip->fill(CRGB::Black); } -void solidWhite(VirtualStrip *strip) +void solidWhite(VirtualStrip *strip) { - fill_solid( strip->leds, strip->length(), CRGB::White); + strip->fill(CRGB::White); } void solidRed(VirtualStrip *strip) { - fill_solid( strip->leds, strip->length(), CRGB::Red); + strip->fill(CRGB::Red); } void solidBlue(VirtualStrip *strip) { - fill_solid( strip->leds, strip->length(), CRGB::Blue); + strip->fill(CRGB::Blue); } void confetti(VirtualStrip *strip) { strip->darken(2); - int pos = random16(strip->length()); + int pos = random16(strip->num_leds); strip->leds[pos] += strip->palette_color(random8(64), strip->hue); } @@ -65,8 +65,8 @@ void biwave(VirtualStrip *strip) uint16_t r = strip->frame * 32; r = cos16( r + random_offset ) + 32768; - uint8_t p1 = scaled16to8(l, 0, strip->length()-1); - uint8_t p2 = scaled16to8(r, 0, strip->length()-1); + uint8_t p1 = scaled16to8(l, 0, strip->num_leds-1); + uint8_t p2 = scaled16to8(r, 0, strip->num_leds-1); if (p2 < p1) { uint16_t t = p1; @@ -90,14 +90,14 @@ void sinelon(VirtualStrip *strip) // a colored dot sweeping back and forth, with fading trails strip->darken(30); - int pos = scale16(sin16( strip->frame << 5 ) + 32768, strip->length()-1); // beatsin16 re-implemented + int pos = scale16(sin16( strip->frame << 5 ) + 32768, strip->num_leds-1); // beatsin16 re-implemented strip->leds[pos] += strip->hue_color(); } void bpm_palette(VirtualStrip *strip) { uint8_t beat = strip->bpm_sin16(64, 255); - for (int i = 0; i < strip->length(); i++) { + for (auto i = 0; i < strip->num_leds; i++) { CRGB c = strip->palette_color(i*2, strip->hue, beat-strip->hue+(i*10)); strip->leds[i] = c; } @@ -109,7 +109,7 @@ void bpm(VirtualStrip *strip) CRGBPalette16 palette = PartyColors_p; uint8_t beat = strip->bpm_sin16(64, 255); - for (int i = 0; i < strip->length(); i++) { + for (auto i = 0; i < strip->num_leds; i++) { strip->leds[i] = ColorFromPalette(palette, strip->hue+(i*2), beat-strip->hue+(i*10)); } } @@ -120,17 +120,17 @@ void juggle(VirtualStrip *strip) strip->darken(5); byte dothue = 0; - for( int i = 0; i < 8; i++) { + for (auto i = 0; i < 8; i++) { CRGB c = strip->palette_color(dothue + strip->hue); // c = CHSV(dothue, 200, 255); - strip->leds[beatsin16( i+7, 0, strip->length()-1 )] |= c; + strip->leds[beatsin16( i+7, 0, strip->num_leds-1 )] |= c; dothue += 32; } } -uint8_t noise[MAX_VIRTUAL_LEDS]; +uint8_t noise[MAX_VIRTUAL_LEDS] = {0}; -void fillnoise8(uint32_t frame, uint8_t num_leds) { +void fillnoise8(uint32_t frame, int32_t num_leds) { uint16_t scale = 17; uint8_t dataSmoothing = 240; @@ -139,7 +139,7 @@ void fillnoise8(uint32_t frame, uint8_t num_leds) { num_leds = max_leds; } - for (int i = 0; i < num_leds; i++) { + for (auto i = 0; i < num_leds; i++) { uint8_t data = inoise8(i * scale, frame>>2); // The range of the inoise8 function is roughly 16-238. @@ -156,9 +156,9 @@ void fillnoise8(uint32_t frame, uint8_t num_leds) { void drawNoise(VirtualStrip *strip) { // generate noise data - fillnoise8(strip->frame >> 2, strip->length()); + fillnoise8(strip->frame >> 2, strip->num_leds); - for(int i = 0; i < strip->length(); i++) { + for (auto i = 0; i < strip->num_leds; i++) { CRGB color = strip->palette_color(noise[i], strip->hue); strip->leds[i] = color; } diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 032599053f..13498bd709 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -45,6 +45,7 @@ class VirtualStrip { public: CRGB leds[MAX_VIRTUAL_LEDS]; + uint16_t num_leds = 1; // only temporary until the first loop uint8_t brightness; // Fade in/out @@ -66,14 +67,7 @@ class VirtualStrip { fade = Dead; } - int32_t length() const { - // Try to be the same as the main segment, but not if it's too big - auto len = strip.getMainSegment().length(); - if (len > MAX_VIRTUAL_LEDS) - return MAX_VIRTUAL_LEDS; - return len; - } - + void load(Background &b, uint8_t fs=DEFAULT_FADE_SPEED) { background = b; @@ -97,12 +91,12 @@ class VirtualStrip { void darken(uint8_t amount=10) { - fadeToBlackBy( leds, length(), amount); + fadeToBlackBy( leds, num_leds, amount); } void fill(CRGB crgb) { - fill_solid( leds, length(), crgb); + fill_solid( leds, num_leds, crgb); } void update(BeatFrame_24_8 fr, uint8_t bp) @@ -112,6 +106,13 @@ class VirtualStrip { frame = fr; + // Try to keep our number of LEDs as the same as the main segment, + // but not if it's too big for the buffer array. + auto len = strip.getMainSegment().length(); + if (len > MAX_VIRTUAL_LEDS) + len = MAX_VIRTUAL_LEDS; + num_leds = len; + switch (this->background.sync) { case All: break; @@ -190,7 +191,7 @@ class VirtualStrip { } CRGB getPixelColor(int32_t pos) const { - return leds[pos % length()]; + return leds[pos % num_leds]; } }; From fcee6ab6139732498a17256152bdc191b12b7810 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 19 Aug 2024 10:33:59 -0700 Subject: [PATCH 248/263] some basic constructor cleanup intialize struct variables remove double initialization disabled espnow debug counters --- usermods/Tubes/options.h | 4 ++-- usermods/Tubes/virtual_strip.h | 8 ++++---- wled00/espnow_broadcast.cpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/usermods/Tubes/options.h b/usermods/Tubes/options.h index 6daac941f8..0c4b28e7ea 100644 --- a/usermods/Tubes/options.h +++ b/usermods/Tubes/options.h @@ -28,8 +28,8 @@ typedef struct ControlParameters { ControlParameters(Duration d=MediumDuration, Energy e=Chill) : duration(d), energy(e) {}; public: - Duration duration=MediumDuration; - Energy energy=Chill; + Duration duration; + Energy energy; } ControlParams; typedef enum PenMode: uint8_t { diff --git a/usermods/Tubes/virtual_strip.h b/usermods/Tubes/virtual_strip.h index 13498bd709..4e4232d18d 100644 --- a/usermods/Tubes/virtual_strip.h +++ b/usermods/Tubes/virtual_strip.h @@ -16,10 +16,10 @@ typedef void (*BackgroundFn)(VirtualStrip *strip); class Background { public: - BackgroundFn animate; - uint8_t wled_fx_id; - uint8_t palette_id; - SyncMode sync=All; + BackgroundFn animate {nullptr}; + uint8_t wled_fx_id {0}; + uint8_t palette_id {0}; + SyncMode sync {All}; }; typedef enum VirtualStripFade { diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 6700bb5672..68d5593f87 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -34,7 +34,7 @@ ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); //#define ESPNOW_DEBUGGING //#define ESPNOW_CALLBACK_DEBUGGING // Serial is called from multiple threads -#define ESPNOW_DEBUG_COUNTERS +//#define ESPNOW_DEBUG_COUNTERS #define BROADCAST_ADDR_ARRAY_INITIALIZER {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define WLED_ESPNOW_WIFI_CHANNEL 1 From eb87f63a61f9f8936485342152fac75fc65aa2b7 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Mon, 19 Aug 2024 19:27:08 -0700 Subject: [PATCH 249/263] Revert to 5.3.0 IDF for now --- platformio_tubes.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 629f5fa374..352e7bce95 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -6,8 +6,8 @@ ; -- ir remotes ; For devices with those inputs use the [tubes] settings ; which adds those back in -platform = espressif32@6.8.1 -platform_packages = framework-arduinoespressif32 @ 3.20014.231204 +platform = espressif32@5.3.0 +platform_packages = ;platform_packages = ;framework-arduinoespressif32 @ 3.20017.0 build_unflags = -D LOROL_LITTLEFS From 7713d411adee05a571cfdb8c2b88bd3cb85cae13 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Mon, 19 Aug 2024 20:39:22 -0700 Subject: [PATCH 250/263] restart ESPNOW after WiFi AP stopped --- usermods/Tubes/controller.h | 9 ++++++++- wled00/espnow_broadcast.cpp | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 8211a05029..4be21d0cce 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -1009,7 +1009,14 @@ class PatternController : public MessageReceiver { } setBrightness(arg >> 8); return; - + case 'a': + Serial.println("Turning on WiFi access point."); + WLED::instance().initAP(true); + return; + case 'q': + Serial.println("Turning off WiFi access point."); + WiFi.disconnect(true); + return; case 'b': if (arg < 60*256) { Serial.println(F("nope")); diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 68d5593f87..450355c7ee 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -2,7 +2,7 @@ #ifndef WLED_DISABLE_ESPNOW_NEW #include #include - +#include #if defined ESP32 #include #include @@ -143,6 +143,7 @@ bool ESPNOWBroadcast::setup() { Serial.printf("esp_event_loop_create_default() err %d\n", err); return false; } + err = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); if ( ESP_OK != err ) { Serial.printf("esp_event_handler_instance_register(WIFI_EVENT_STA_START) err %d\n", err); @@ -158,6 +159,12 @@ bool ESPNOWBroadcast::setup() { Serial.printf("esp_event_handler_instance_register(WIFI_EVENT_AP_START) err %d\n", err); return false; } + + // err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); + // if ( ESP_OK != err ) { + // Serial.printf("esp_event_handler_instance_register(ESP_EVENT_ANY_ID) err %d\n", err); + // return false; + // } #endif setup = espnowBroadcastImpl.setupWiFi(); @@ -382,6 +389,8 @@ void ESPNOWBroadcastImpl::onWiFiEvent(void* arg, esp_event_base_t event_base, in } case WIFI_EVENT_STA_STOP: + lastReconnectAttempt = 0; + // fall thru case WIFI_EVENT_AP_START: { ESPNOWBroadcast::STATE started {ESPNOWBroadcast::STARTED}; ESPNOWBroadcast::STATE starting {ESPNOWBroadcast::STARTING}; From 3976a57de2af2f160b41f34d238a0587caad9533 Mon Sep 17 00:00:00 2001 From: Craig Link Date: Tue, 20 Aug 2024 08:55:06 -0700 Subject: [PATCH 251/263] athom-c3 fixes --- platformio_tubes.ini | 21 +++++++++++++-------- wled00/espnow_broadcast.cpp | 29 +++++++++++++++++++---------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 352e7bce95..1faf04fc72 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -62,6 +62,7 @@ lib_ignore = ${env:esp32_quinled_diguno.lib_ignore} lib_deps = ${env:esp32_quinled_diguno.lib_deps} IRremoteESP8266 @ 2.8.6 + [env:esp32_quinled_dig2go_tubes] extends = env:esp32_quinled_dig2go platform = ${tubes.platform} @@ -69,7 +70,7 @@ platform_packages = ${tubes.platform_packages} build_unflags = ${tubes.build_unflags} ${env:esp32_quinled_dig2go} -build_flags = +build_flags = -D WLED_RELEASE_NAME=DIG2GO_TUBES ${tubes.build_flags} ${env:esp32_quinled_dig2go.build_flags} -D FASTLED_ESP32_SPI_BUS=HSPI @@ -96,7 +97,7 @@ build_unflags = ${env:esp32s3dev_8MB_PSRAM_opi.build_unflags} -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=esp32-s3-matrix-m1 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32_S3_MATRIX_M1 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 @@ -111,8 +112,10 @@ lib_ignore = ${env:esp32s3dev_8MB_PSRAM_opi.lib_ignore} [env:esp32-s3-matrix-m1_tubes] extends = env:esp32-s3-matrix-m1 -platform = ${tubes.platform} -platform_packages = ${tubes.platform_packages} +platform = espressif32@6.8.1 +platform_packages = +;platform = ${tubes.platform} +;platform_packages = ${tubes.platform_packages} board_build.partitions = tools/WLED_ESP32_4MB_noOTA.csv build_unflags = ${env:esp32-s3-matrix-m1.build_unflags} ${tubes_no_mic.build_unflags} @@ -142,17 +145,19 @@ build_flags = ${env:esp32c3dev.build_flags} [env:esp32-c3-athom_tubes] extends = env:esp32-c3-athom -platform = ${tubes_no_mic.platform} -platform_packages = ${tubes_no_mic.platform_packages} +platform = espressif32@6.8.1 +platform_packages = +;platform = ${tubes_no_mic.platform} +;platform_packages = ${tubes_no_mic.platform_packages} build_unflags = ${env:esp32-c3-athom.build_unflags} ${tubes_no_mic.build_unflags} -build_flags = +build_flags = ${common.build_flags} -D WLED_RELEASE_NAME=ESP32_C3_ATHOM_TUBES ${tubes_no_mic.build_flags} - -D WLED_WATCHDOG_TIMEOUT=0 -D LEDPIN=10 -D BTNPIN=9 -D FASTLED_ESP32_SPI_BUS=HSPI -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 + ;-D WLED_WIFI_POWER_SETTING=WIFI_POWER_8_5dBm lib_ignore = ${env:esp32-c3-athom.lib_ignore} ${tubes_no_mic.lib_ignore} lib_deps = diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 450355c7ee..f145d1debd 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -34,13 +34,14 @@ ESP_EVENT_DEFINE_BASE(SYSTEM_EVENT); //#define ESPNOW_DEBUGGING //#define ESPNOW_CALLBACK_DEBUGGING // Serial is called from multiple threads +//#define ESPNOW_EVENT_DEBUGGING //#define ESPNOW_DEBUG_COUNTERS #define BROADCAST_ADDR_ARRAY_INITIALIZER {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define WLED_ESPNOW_WIFI_CHANNEL 1 #ifndef WLED_WIFI_POWER_SETTING -#define WLED_WIFI_POWER_SETTING WIFI_POWER_15dBm +#define WLED_WIFI_POWER_SETTING WIFI_POWER_18_5dBm #endif typedef struct { @@ -144,6 +145,13 @@ bool ESPNOWBroadcast::setup() { return false; } + #ifdef ESPNOW_EVENT_DEBUGGING + err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); + if ( ESP_OK != err ) { + Serial.printf("esp_event_handler_instance_register(ESP_EVENT_ANY_ID) err %d\n", err); + return false; + } + #else err = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); if ( ESP_OK != err ) { Serial.printf("esp_event_handler_instance_register(WIFI_EVENT_STA_START) err %d\n", err); @@ -159,13 +167,8 @@ bool ESPNOWBroadcast::setup() { Serial.printf("esp_event_handler_instance_register(WIFI_EVENT_AP_START) err %d\n", err); return false; } - - // err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, ESPNOWBroadcastImpl::onWiFiEvent, nullptr, nullptr); - // if ( ESP_OK != err ) { - // Serial.printf("esp_event_handler_instance_register(ESP_EVENT_ANY_ID) err %d\n", err); - // return false; - // } -#endif + #endif // ESPNOW_EVENT_DEBUGGING +#endif ESP_IDF_VERSION setup = espnowBroadcastImpl.setupWiFi(); #endif //ESP32 @@ -190,6 +193,7 @@ bool ESPNOWBroadcastImpl::setupWiFi() { return false; } + yield(); return true; } #endif //ESP32 @@ -302,11 +306,13 @@ void ESPNOWBroadcastImpl::start() { auto status = WiFi.status(); if ( status >= WL_DISCONNECTED ) { if (esp_wifi_start() == ESP_OK) { + yield(); if (!WiFi.setTxPower(WLED_WIFI_POWER_SETTING)) { auto power = WiFi.getTxPower(); Serial.printf("setTxPower(%d) failed. getTX: %d\n", WLED_WIFI_POWER_SETTING, power); } if (esp_now_init() == ESP_OK) { + yield(); if (esp_now_register_recv_cb(ESPNOWBroadcastImpl::onESPNowRxCallback) == ESP_OK) { static esp_now_peer_info_t peer = { BROADCAST_ADDR_ARRAY_INITIALIZER, @@ -376,9 +382,9 @@ void ESPNOWBroadcastImpl::onWiFiEvent(void* arg, esp_event_base_t event_base, in #ifdef ESPNOW_DEBUGGING #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) - Serial.printf("WiFiEvent( %d )\n", event_id ); + Serial.printf("WiFiEvent( %d )\n", event_id ); #else - Serial.printf("WiFiEvent( %s )\n", WiFi.eventName((arduino_event_id_t)event_id) ); + Serial.printf("WiFiEvent( %s )\n", WiFi.eventName((arduino_event_id_t)event_id) ); #endif #endif switch (event_id) { @@ -443,6 +449,9 @@ void ESPNOWBroadcastImpl::onESPNowRxCallback(const uint8_t *mac, const uint8_t * if (espnowBroadcastImpl._rxFilter) { if (!espnowBroadcastImpl._rxFilter(mac, data, len, rssi)) { +#ifdef ESPNOW_CALLBACK_DEBUGGING + Serial.printf("Filtering message of len %d\n", len); +#endif return; } } From 95ced63eb0db65c8ad63e1804ca6c95fa93a4d9a Mon Sep 17 00:00:00 2001 From: Craig Link Date: Tue, 20 Aug 2024 08:58:10 -0700 Subject: [PATCH 252/263] out WLED_RELEASE_NAME macro to info json --- wled00/json.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wled00/json.cpp b/wled00/json.cpp index af8ed89ef6..9e835d1382 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -609,6 +609,9 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme } } +#define _MACRO_TO_STR(x) #x +#define MACRO_TO_STR(x) _MACRO_TO_STR(x) + void serializeInfo(JsonObject root) { root[F("ver")] = versionString; @@ -791,6 +794,7 @@ void serializeInfo(JsonObject root) root[F("brand")] = "WLED"; root[F("product")] = F("FOSS"); + root[F("release")] = MACRO_TO_STR(WLED_RELEASE_NAME); root["mac"] = escapedMac; char s[16] = ""; if (Network.isConnected()) From 85709057808ce2c6a3bdf0cef8f58c766550faae Mon Sep 17 00:00:00 2001 From: Craig Link Date: Tue, 20 Aug 2024 11:36:13 -0700 Subject: [PATCH 253/263] yield for c3 network startup --- platformio_tubes.ini | 7 ++++--- wled00/espnow_broadcast.cpp | 4 ++-- wled00/wled.cpp | 14 +++++++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 1faf04fc72..6491a724f2 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -70,6 +70,7 @@ platform_packages = ${tubes.platform_packages} build_unflags = ${tubes.build_unflags} ${env:esp32_quinled_dig2go} + -D WLED_RELEASE_NAME build_flags = -D WLED_RELEASE_NAME=DIG2GO_TUBES ${tubes.build_flags} ${env:esp32_quinled_dig2go.build_flags} @@ -97,7 +98,7 @@ build_unflags = ${env:esp32s3dev_8MB_PSRAM_opi.build_unflags} -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32_S3_MATRIX_M1 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_MATRIX_M1 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 @@ -151,13 +152,13 @@ platform_packages = ;platform_packages = ${tubes_no_mic.platform_packages} build_unflags = ${env:esp32-c3-athom.build_unflags} ${tubes_no_mic.build_unflags} -build_flags = ${common.build_flags} -D WLED_RELEASE_NAME=ESP32_C3_ATHOM_TUBES +build_flags = ${common.build_flags} -D WLED_RELEASE_NAME=ESP32-C3_ATHOM_TUBES ${tubes_no_mic.build_flags} -D LEDPIN=10 -D BTNPIN=9 -D FASTLED_ESP32_SPI_BUS=HSPI -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 - ;-D WLED_WIFI_POWER_SETTING=WIFI_POWER_8_5dBm + -D WLED_WIFI_POWER_SETTING=WIFI_POWER_15dBm lib_ignore = ${env:esp32-c3-athom.lib_ignore} ${tubes_no_mic.lib_ignore} lib_deps = diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index f145d1debd..842b22fd1d 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -168,7 +168,7 @@ bool ESPNOWBroadcast::setup() { return false; } #endif // ESPNOW_EVENT_DEBUGGING -#endif ESP_IDF_VERSION +#endif // ESP_IDF_VERSION setup = espnowBroadcastImpl.setupWiFi(); #endif //ESP32 @@ -381,7 +381,7 @@ void ESPNOWBroadcastImpl::onWiFiEvent(void* arg, esp_event_base_t event_base, in if ( event_base == WIFI_EVENT ) { #ifdef ESPNOW_DEBUGGING - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) Serial.printf("WiFiEvent( %d )\n", event_id ); #else Serial.printf("WiFiEvent( %s )\n", WiFi.eventName((arduino_event_id_t)event_id) ); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 657339b11c..98ba39426f 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -542,8 +542,9 @@ void WLED::beginStrip() void WLED::initAP(bool resetAP) { - if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) + if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) { return; + } if (resetAP) { WLED_SET_AP_SSID(); @@ -557,10 +558,17 @@ void WLED::initAP(bool resetAP) WiFi.setTxPower(WIFI_POWER_8_5dBm); #endif + Serial.printf("WLED::initAP - apActive: %s", apActive ? "true" : "false"); + if (!apActive) // start captive portal if AP active { + yield(); + Serial.println("WLED::initAP - starting captive portal"); + DEBUG_PRINTLN(F("Init AP interfaces")); server.begin(); + yield(); + if (udpPort > 0 && udpPort != ntpLocalPort) { udpConnected = notifierUdp.begin(udpPort); } @@ -570,9 +578,13 @@ void WLED::initAP(bool resetAP) if (udpPort2 > 0 && udpPort2 != ntpLocalPort && udpPort2 != udpPort && udpPort2 != udpRgbPort) { udp2Connected = notifier2Udp.begin(udpPort2); } + yield(); + e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); ddp.begin(false, DDP_DEFAULT_PORT); + yield(); + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); dnsServer.start(53, "*", WiFi.softAPIP()); } From 1520f7986b8ec078a37e4d35a4a6920946dd858c Mon Sep 17 00:00:00 2001 From: Craig Link Date: Tue, 20 Aug 2024 13:39:10 -0700 Subject: [PATCH 254/263] update base on board variant and reset the wifi after update --- usermods/Tubes/firmware.sh | 38 +++++++++++++++--- usermods/Tubes/firmware_test.sh | 68 +++++++++++++++++++++++++++++++++ wled00/espnow_broadcast.cpp | 2 +- wled00/json.cpp | 4 -- wled00/wled.cpp | 10 +++-- 5 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 usermods/Tubes/firmware_test.sh diff --git a/usermods/Tubes/firmware.sh b/usermods/Tubes/firmware.sh index 20a8a544cb..a8bd246411 100755 --- a/usermods/Tubes/firmware.sh +++ b/usermods/Tubes/firmware.sh @@ -40,11 +40,39 @@ update_config() { } update_firmware() { - echo "Updating firmware via OTA" - curl -s -F "update=@../../build_output/firmware/tubes.bin" -H "Connection: close" --no-keepalive $1/update >/dev/null - echo "Updated; wait..." - sleep 5 - update_config $1 + echo "Getting info" + json=$( curl -s http://$1/json/si ) + + arch=$(echo "$json" | jq -r '.["info"].arch') + name=$(echo "$json" | jq -r '.["info"].name') + echo "arch: $arch name: $name" + + firmware= + + if [ ! -z "$name" ]; then + if [ "dig2go" == "$name" ]; then + firmware="esp32_quinled_dig2go_tubes.bin" + fi + fi + + if [ -z "$firmware" ] && [ ! -z "$arch" ]; then + if [ "ESP32-C3" == "$arch" ]; then + firmware="esp32-c3-athom_tubes.bin" + elif [ "esp32" == "$arch" ]; then + firmware="esp32_quinled_dig2go_tubes.bin" + fi + fi + + if [ -z "$firmware" ]; then + echo "firmware not set - not updating OTA" + curl -s http://$1/reset -H "Connection: close" >/dev/null + else + echo "Updating $firmware firmware via OTA" + curl -s -F "update=@../../build_output/firmware/$firmware" -H "Connection: close" --no-keepalive $1/update >/dev/null + echo "Updated; wait..." + sleep 5 + update_config $1 + fi } connect() { diff --git a/usermods/Tubes/firmware_test.sh b/usermods/Tubes/firmware_test.sh new file mode 100644 index 0000000000..bdfcd3462f --- /dev/null +++ b/usermods/Tubes/firmware_test.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Updates new boards (which start as broadcasting on WLED-AP) to custom firmware +# Will update as many boards as are plugged in, one at a time. + + +# Two ways to use it: +# 1) ./firmware.sh +# This will update all boards with open APs named WLED-AP +# 2) ./firmware.sh batch +# This will update all boards with open APs named WLED-AP or WLED-UPDATE +# This is useful for updating a batch of boards at once +# To put a board in this mode, send it a V## command with a version higher than the current one +# There's also: +# 3) ./firmware.sh upload +# This will upload the firmware to the internet server, but not update any boards +# Internet upload is not working 100% yet, so this is not recommended + + +WLEDPATH=../../build_output/firmware +ESPPATH=~/.platformio/packages/framework-arduinoespressif32/tools + +update_config() { + # No longer update configs? comment this + # return; + + echo "Updating configuration via OTA" +# curl -s http://$1/upload -F "data=@default_config.json;filename=/cfg.json" -H "Connection: close" --no-keepalive + echo "Configured; wait..." +# curl -s http://$1/reset -H "Connection: close" >/dev/null +} + + +update_firmware() { + + echo "Getting info" + json=$( curl -s http://$1/json/si ) + + arch=$(echo "$json" | jq -r '.["info"].arch') + name=$(echo "$json" | jq -r '.["info"].name') + echo "$arch $name" + + firmware= + + if [ ! -z "$name" ]; then + if [ "dig2go" == "$name" ]; then + firmware="esp32_quinled_dig2go_tubes.bin" + fi + fi + + if [ -z "$firmware" ] && [ ! -z "$arch" ]; then + if [ "ESP32-C3" == "$arch" ]; then + firmware="esp32-c3-athom_tubes.bin" + elif [ "esp32" == "$arch" ]; then + firmware="esp32_quinled_dig2go_tubes.bin" + fi + fi + + if [ -z "$firmware" ]; then + echo "firmware not set - not updating OTA" + curl -s http://$1/reset -H "Connection: close" >/dev/null + else + echo "Updating $firmware firmware via OTA" + curl -s -F "update=@../../build_output/firmware/$firmware" -H "Connection: close" --no-keepalive $1/update >/dev/null + fi +} + +update_firmware $1 diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index 842b22fd1d..b7bb9f4afe 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -381,7 +381,7 @@ void ESPNOWBroadcastImpl::onWiFiEvent(void* arg, esp_event_base_t event_base, in if ( event_base == WIFI_EVENT ) { #ifdef ESPNOW_DEBUGGING - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4) Serial.printf("WiFiEvent( %d )\n", event_id ); #else Serial.printf("WiFiEvent( %s )\n", WiFi.eventName((arduino_event_id_t)event_id) ); diff --git a/wled00/json.cpp b/wled00/json.cpp index 9e835d1382..af8ed89ef6 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -609,9 +609,6 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme } } -#define _MACRO_TO_STR(x) #x -#define MACRO_TO_STR(x) _MACRO_TO_STR(x) - void serializeInfo(JsonObject root) { root[F("ver")] = versionString; @@ -794,7 +791,6 @@ void serializeInfo(JsonObject root) root[F("brand")] = "WLED"; root[F("product")] = F("FOSS"); - root[F("release")] = MACRO_TO_STR(WLED_RELEASE_NAME); root["mac"] = escapedMac; char s[16] = ""; if (Network.isConnected()) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 98ba39426f..ce9e00169a 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -30,6 +30,13 @@ void WLED::reset() } applyBri(); DEBUG_PRINTLN(F("WLED RESET")); + + WiFi.softAPdisconnect(true); + WiFi.disconnect(true); + yield(); + + lastReconnectAttempt = 0; + apActive = false; ESP.restart(); } @@ -558,12 +565,9 @@ void WLED::initAP(bool resetAP) WiFi.setTxPower(WIFI_POWER_8_5dBm); #endif - Serial.printf("WLED::initAP - apActive: %s", apActive ? "true" : "false"); - if (!apActive) // start captive portal if AP active { yield(); - Serial.println("WLED::initAP - starting captive portal"); DEBUG_PRINTLN(F("Init AP interfaces")); server.begin(); From 0304ca7780102b4fc3f89e80b9c40c99acd21f5e Mon Sep 17 00:00:00 2001 From: Craig Link Date: Tue, 20 Aug 2024 18:28:18 -0700 Subject: [PATCH 255/263] reduced attenna power seems to make c3 more stable --- platformio_tubes.ini | 5 +++-- usermods/Tubes/firmware_test.sh | 1 + usermods/Tubes/node.h | 4 ++++ wled00/espnow_broadcast.cpp | 2 +- wled00/wled.cpp | 1 - 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 6491a724f2..3a27881cc2 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -152,13 +152,14 @@ platform_packages = ;platform_packages = ${tubes_no_mic.platform_packages} build_unflags = ${env:esp32-c3-athom.build_unflags} ${tubes_no_mic.build_unflags} -build_flags = ${common.build_flags} -D WLED_RELEASE_NAME=ESP32-C3_ATHOM_TUBES +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3_ATHOM_TUBES ${tubes_no_mic.build_flags} -D LEDPIN=10 -D BTNPIN=9 -D FASTLED_ESP32_SPI_BUS=HSPI -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 - -D WLED_WIFI_POWER_SETTING=WIFI_POWER_15dBm + -D WLED_WIFI_POWER_SETTING=WIFI_POWER_8_5dBm + -D LOLIN_WIFI_FIX lib_ignore = ${env:esp32-c3-athom.lib_ignore} ${tubes_no_mic.lib_ignore} lib_deps = diff --git a/usermods/Tubes/firmware_test.sh b/usermods/Tubes/firmware_test.sh index bdfcd3462f..ed284f8dde 100644 --- a/usermods/Tubes/firmware_test.sh +++ b/usermods/Tubes/firmware_test.sh @@ -62,6 +62,7 @@ update_firmware() { else echo "Updating $firmware firmware via OTA" curl -s -F "update=@../../build_output/firmware/$firmware" -H "Connection: close" --no-keepalive $1/update >/dev/null + curl -s http://$1/reset -H "Connection: close" >/dev/null fi } diff --git a/usermods/Tubes/node.h b/usermods/Tubes/node.h index c22b3af92c..690b75ec69 100644 --- a/usermods/Tubes/node.h +++ b/usermods/Tubes/node.h @@ -350,7 +350,11 @@ class LightNode { void reset(MeshId id = 0) { if (id == 0) { +#if defined(LOLIN_WIFI_FIX) && (defined(ARDUINO_ARCH_ESP32C3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32S3)) + id = random(10, 255); // Leave room at bottom and top of 12 bits +#else id = random(256, 4000); // Leave room at bottom and top of 12 bits +#endif } header.id = id; follow(NULL); diff --git a/wled00/espnow_broadcast.cpp b/wled00/espnow_broadcast.cpp index b7bb9f4afe..c0c789f85e 100644 --- a/wled00/espnow_broadcast.cpp +++ b/wled00/espnow_broadcast.cpp @@ -384,7 +384,7 @@ void ESPNOWBroadcastImpl::onWiFiEvent(void* arg, esp_event_base_t event_base, in #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4) Serial.printf("WiFiEvent( %d )\n", event_id ); #else - Serial.printf("WiFiEvent( %s )\n", WiFi.eventName((arduino_event_id_t)event_id) ); + Serial.printf("WiFiEvent %d ( %s )\n", event_id, WiFi.eventName((arduino_event_id_t)event_id) ); #endif #endif switch (event_id) { diff --git a/wled00/wled.cpp b/wled00/wled.cpp index ce9e00169a..8e35f00af7 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -567,7 +567,6 @@ void WLED::initAP(bool resetAP) if (!apActive) // start captive portal if AP active { - yield(); DEBUG_PRINTLN(F("Init AP interfaces")); server.begin(); From 0166dae8186a47acd3987e7ce77888d63583f521 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 26 Nov 2024 22:59:48 -0800 Subject: [PATCH 256/263] default power save off; better help/debugging --- usermods/Tubes/controller.h | 9 +++++++-- usermods/Tubes/debug.h | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index 4be21d0cce..b655f96c53 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -114,7 +114,7 @@ class PatternController : public MessageReceiver { uint8_t patternOverride = 0; uint16_t wled_fader = 0; ControllerRole role; - bool power_save = true; // Power save ALWAYS starts on. Some roles just ignore it + bool power_save = false; // Default to power save mode OFF but 3 sec press turns it on uint8_t flashColor = 0; AutoUpdater updater = AutoUpdater(); @@ -986,7 +986,7 @@ class PatternController : public MessageReceiver { case '~': doReboot = true; break; - case '@': + case '_': togglePowerSave(); break; @@ -1078,6 +1078,7 @@ class PatternController : public MessageReceiver { case '*': case '(': case ')': + case '@': case 'G': case 'A': case 'W': @@ -1125,6 +1126,7 @@ class PatternController : public MessageReceiver { Serial.println(F("m### - sync mode")); Serial.println(F("c### - colors")); Serial.println(F("e### - effects")); + Serial.println(F("n - force next")); Serial.println(); Serial.println(F("i### - set ID")); Serial.println(F("d - toggle debugging")); @@ -1133,6 +1135,9 @@ class PatternController : public MessageReceiver { Serial.println("U - begin auto-update"); Serial.println("P - toggle all power saves"); Serial.println("O - toggle all sound overlays"); + Serial.println("==== wifi ===="); + Serial.println("a - turn on access point"); + Serial.println("q - turn off access point"); Serial.println("==== global actions ===="); Serial.println("* - enter select mode (double-click to Ready)"); Serial.println("A - turn on access point (Ready to update)"); diff --git a/usermods/Tubes/debug.h b/usermods/Tubes/debug.h index ba5d234801..b4af3920f3 100644 --- a/usermods/Tubes/debug.h +++ b/usermods/Tubes/debug.h @@ -81,7 +81,8 @@ class DebugController { auto seg = strip.getMainSegment(); extractModeName(seg.mode, JSON_mode_names, mode_name, 50); extractModeName(seg.palette, JSON_palette_names, palette_name, 50); - Serial.printf("=== WLED: %s(%u) %s(%u) speed:%u intensity:%u", + Serial.printf("=== WLED: %d LEDs, %s(%u) %s(%u) speed:%u intensity:%u", + strip.getLengthTotal(), mode_name, seg.mode, palette_name, From 077377eab9efebf793a99cf129a4221d723a8907 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sat, 1 Feb 2025 16:04:17 -0800 Subject: [PATCH 257/263] Create RUBY mode --- usermods/Tubes/controller.h | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index b655f96c53..d98e27f58d 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -161,7 +161,7 @@ class PatternController : public MessageReceiver { } bool isMasterRole() const { -#if defined(GOLDEN) || defined(CHRISTMAS) +#if defined(GOLDEN) || defined(CHRISTMAS) || defined(RUBY) return true; #endif return role >= MasterRole; @@ -214,7 +214,7 @@ class PatternController : public MessageReceiver { } else { options.brightness = DEFAULT_TUBE_BRIGHTNESS; } -#if defined(GOLDEN) || defined(CHRISTMAS) +#if defined(GOLDEN) || defined(CHRISTMAS) || defined(RUBY) node.reset(0xFFF); #endif options.debugging = false; @@ -446,7 +446,29 @@ class PatternController : public MessageReceiver { uint8_t r = blend8(c.r, color2.r, fader); uint8_t g = blend8(c.g, color2.g, fader); uint8_t b = blend8(c.b, color2.b, fader); +#ifdef RUBY + // Simple average brightness for a "luminosity" measure + uint8_t brightness = (uint16_t)(r + g + b) / 3; + + // Check if it's near white (all channels fairly similar and somewhat bright) + // You can tweak thresholds to taste. + bool isNearWhite = (abs(r - g) < 20 && abs(g - b) < 20 && (r + g + b) > 200); + + // Force everything into a shade of red: + uint8_t redLevel = brightness; + uint8_t greenLevel = 0; + uint8_t blueLevel = 0; + + // If it’s near white, add a little G/B so it’s not pure red. + if(isNearWhite) { + greenLevel = brightness / 2; + blueLevel = brightness / 2; + } + + c = CRGB(redLevel, greenLevel, blueLevel); +#else c = CRGB(r,g,b); +#endif } strip.setPixelColor(i, c); } @@ -678,6 +700,13 @@ class PatternController : public MessageReceiver { /*best yes:*/25, 34, 34, 61, 63, 81, 112, /*maybe:*/81, 28, 107}; next_state.palette_id = colors[r]; +#elif defined(RUBY) // 81, 107 are too bright + uint r = random8(0, 20); + uint colors[20] = {/*gold:*/, + /*yes:*/21, + /*best yes:*/, + /*maybe:*/33, 35, 44, 81, 93, 107; + next_state.palette_id = colors[r]; #else // Don't select the built-in palettes next_state.palette_id = random8(6, gGradientPaletteCount); From 247b940b80eeac16167adf467e45c3894b89a494 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Sun, 27 Jul 2025 13:55:31 -0700 Subject: [PATCH 258/263] Pin certain libs, since their projects have moved on for 15 and we're not there yet --- platformio_tubes.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 3a27881cc2..68df165991 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -40,7 +40,7 @@ lib_ignore = lib_deps = fastled/FastLED @ ^3.7.0 makuna/NeoPixelBus @ ^2.7.8 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 [tubes] extends = tubes_no_mic @@ -49,7 +49,7 @@ build_flags = ${esp32.AR_build_flags} lib_deps = ${tubes_no_mic.lib_deps} IRremoteESP8266 @ ^2.8.6 - https://github.com/kosme/arduinoFFT @ 2.0.2 ; ${esp32.AR_LIB_deps} + https://github.com/kosme/arduinoFFT#v2.0.2 [env:esp32_quinled_dig2go] From e1796ca60748979dc6230facbf730c5b48523151 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Tue, 12 Aug 2025 21:19:28 -0700 Subject: [PATCH 259/263] override WLED and check in platformio_override.ini since everyone needs it --- .gitignore | 1 - platformio_override.ini | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 platformio_override.ini diff --git a/.gitignore b/.gitignore index c85fae0c22..b687696b95 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ .vscode esp01-update.sh -platformio_override.ini replace_fs.py wled-update.sh diff --git a/platformio_override.ini b/platformio_override.ini new file mode 100644 index 0000000000..0ff991f78f --- /dev/null +++ b/platformio_override.ini @@ -0,0 +1,17 @@ +[platformio] +default_envs = esp32dev +extra_configs = + platformio_tubes.ini + +[env:golden] +extends = env:esp32_quinled_dig2go_tubes +build_flags = ${env:tubes.build_flags} -D GOLDEN + +[env:christmas] +extends = env:esp32_quinled_dig2go_tubes +build_flags = ${env:tubes.build_flags} -D CHRISTMAS + +[env:ruby] +extends = env:esp32_quinled_dig2go_tubes +build_flags = ${env:tubes.build_flags} -D RUBY + From a80fe811a864b2169c3ae83391555d526d952384 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 20 Feb 2026 10:45:11 -0800 Subject: [PATCH 260/263] Enable CHRISTMAS mode and weight holiday palette selection --- platformio_tubes.ini | 1 + usermods/Tubes/controller.h | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 68df165991..3b1bb26811 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -76,6 +76,7 @@ build_flags = -D WLED_RELEASE_NAME=DIG2GO_TUBES ${env:esp32_quinled_dig2go.build_flags} -D FASTLED_ESP32_SPI_BUS=HSPI -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 + -D CHRISTMAS # REMOVE lib_ignore = ESPAsyncTCP ESPAsyncUDP diff --git a/usermods/Tubes/controller.h b/usermods/Tubes/controller.h index d98e27f58d..aca749f21a 100644 --- a/usermods/Tubes/controller.h +++ b/usermods/Tubes/controller.h @@ -694,9 +694,10 @@ class PatternController : public MessageReceiver { uint colors[4] = {18, 58, 71, 111}; next_state.palette_id = colors[r]; #elif defined(CHRISTMAS) // 81, 107 are too bright - uint r = random8(0, 20); - uint colors[20] = {/*gold:*/18, 58, 71, 111, + uint r = random8(0, 26); + uint colors[26] = {/*gold:*/18, 58, 71, 111, /*yes:*/25, 34, 61, 63, 81, 112, + /*yesx2:*/25, 34, 61, 63, 81, 112, /*best yes:*/25, 34, 34, 61, 63, 81, 112, /*maybe:*/81, 28, 107}; next_state.palette_id = colors[r]; From 3ee799f88c67399f5618f29c60aa064a76a23041 Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 20 Feb 2026 10:45:16 -0800 Subject: [PATCH 261/263] Remove explicit ATHOM C3 pin overrides in tubes env --- platformio_tubes.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 3b1bb26811..cad94a37a8 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -153,10 +153,8 @@ platform_packages = ;platform_packages = ${tubes_no_mic.platform_packages} build_unflags = ${env:esp32-c3-athom.build_unflags} ${tubes_no_mic.build_unflags} -build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3_ATHOM_TUBES +build_flags = ${env:esp32-c3-athom.build_flags} -D WLED_RELEASE_NAME=ESP32-C3_ATHOM_TUBES ${tubes_no_mic.build_flags} - -D LEDPIN=10 - -D BTNPIN=9 -D FASTLED_ESP32_SPI_BUS=HSPI -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 -D WLED_WIFI_POWER_SETTING=WIFI_POWER_8_5dBm From b8fb0f0095d843bdbede3adda2fe3d9e8fdc222f Mon Sep 17 00:00:00 2001 From: Steve Eisner Date: Fri, 20 Feb 2026 10:45:29 -0800 Subject: [PATCH 262/263] Add ESP32 GLEDOPTO and tubes build environments --- platformio_tubes.ini | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index cad94a37a8..5917d8a0b7 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -163,3 +163,39 @@ lib_ignore = ${env:esp32-c3-athom.lib_ignore} ${tubes_no_mic.lib_ignore} lib_deps = ${tubes_no_mic.lib_deps} + + +# ------------------------------------------------------------------------------ +# ESP32 GLEDOPTO +# +# LEDs: GRB @ pin 16 +# Button: GPIO 0 +# SR: i2S SD 26, WS 5, SCK 21, MCLK unused +# ------------------------------------------------------------------------------ +[env:esp32-gledopto] +extends = env:esp32dev +lib_ignore = IRremoteESP8266 + ${env:esp32c3dev.lib_ignore} ; ok to keep this line if it exists, harmless +build_flags = + -D LEDPIN=16 + -D BTNPIN=0 + -D WLED_DISABLE_INFRARED + -D IRTYPE=0 + +[env:esp32-gledopto_tubes] +extends = env:esp32-gledopto +platform = platformio/espressif32 @ 6.7.0 +platform_packages = + framework-arduinoespressif32@3.20016.0 ; Arduino-ESP32 2.0.16 +build_unflags = ${env:esp32-gledopto.build_unflags} + ${tubes_no_mic.build_unflags} +build_flags = ${env:esp32-gledopto.build_flags} + ${tubes_no_mic.build_flags} + -D FASTLED_ESP32_SPI_BUS=HSPI + -D NUM_STRIPS=1 -D DEFAULT_LED_COUNT=150 + -D WLED_WIFI_POWER_SETTING=WIFI_POWER_8_5dBm + -D LOLIN_WIFI_FIX +lib_ignore = ${env:esp32-gledopto.lib_ignore} + ${tubes_no_mic.lib_ignore} +lib_deps = ${env:esp32-gledopto.lib_deps} + ${tubes_no_mic.lib_deps} From ad862cc43f06164bb1b2c246acca6cdc81ee748b Mon Sep 17 00:00:00 2001 From: Craig Link Date: Thu, 22 Aug 2024 07:26:41 -0700 Subject: [PATCH 263/263] fastled bug fixes --- platformio_tubes.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/platformio_tubes.ini b/platformio_tubes.ini index 5917d8a0b7..39b499cff4 100644 --- a/platformio_tubes.ini +++ b/platformio_tubes.ini @@ -38,8 +38,8 @@ lib_ignore = ESPAsyncUDP IRremoteESP8266 lib_deps = - fastled/FastLED @ ^3.7.0 - makuna/NeoPixelBus @ ^2.7.8 + fastled/FastLED @ ^3.7.3 + makuna/NeoPixelBus @ ^2.8.0 https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 [tubes] @@ -148,6 +148,7 @@ build_flags = ${env:esp32c3dev.build_flags} [env:esp32-c3-athom_tubes] extends = env:esp32-c3-athom platform = espressif32@6.8.1 +upload_speed = 115200 platform_packages = ;platform = ${tubes_no_mic.platform} ;platform_packages = ${tubes_no_mic.platform_packages}