diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e1da56db..af5bc5c7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -236,7 +236,7 @@ There are breaking changes between 3.5.x and earlier versions of 3.6.0. Please r - fix Table resizing in WebUI [#519](https://github.com/emsesp/EMS-ESP32/issues/519) - allow larger customization files [#570](https://github.com/emsesp/EMS-ESP32/issues/570) -- losing entitiy wwcomfort [#581](https://github.com/emsesp/EMS-ESP32/issues/581) +- losing entity wwcomfort [#581](https://github.com/emsesp/EMS-ESP32/issues/581) ## Changed diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index c85e5572e..52acb627f 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -20,7 +20,7 @@ - boiler Bosch C1200W, id 12, [#1536](https://github.com/emsesp/EMS-ESP32/issues/1536) - mixer MM100 telegram 0x2CC [#1554](https://github.com/emsesp/EMS-ESP32/issues/1554) - boiler hpSetDiffPressure [#1563](https://github.com/emsesp/EMS-ESP32/issues/1563) -- custom variables [#1423](https://github.com/emsesp/EMS-ESP32/issues/1423) +- custom variables [#1423](https://github.com/emsesp/EMS-ESP32/issues/1423) ## Fixed @@ -37,3 +37,4 @@ - use flag for BC400 compatible thermostats, manage different mode settings - HA don't set entity_category to Diagnostic/Configuration for EMS entities [#1459](https://github.com/emsesp/EMS-ESP32/discussions/1459) - upgraded ArduinoJson to 7.0.0 #1538 and then 7.0.2 +- small changes to the API for analog and temperature sensors diff --git a/Makefile b/Makefile index 1beb818c8..74fb42b02 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DAR DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMC_RX_BUFFER_SIZE=1500 DEFINES += $(ARGS) -DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.4-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" +DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.5-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" #---------------------------------------------------------------------- # Sources & Files diff --git a/interface/package.json b/interface/package.json index 3d5c60ddd..3546a18cb 100644 --- a/interface/package.json +++ b/interface/package.json @@ -13,7 +13,9 @@ "build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted", "preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"npm:mock-api\" \"vite preview\"", "mock-api": "bun --watch ../mock-api/server.ts", + "old_mock-api": "bun --watch ../mock-api/server.js", "standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:mock-api\" \"vite\"", + "old_standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:old_mock-api\" \"vite\"", "typesafe-i18n": "typesafe-i18n --no-watch", "webUI": "node progmem-generator.js", "format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'", @@ -21,7 +23,7 @@ }, "dependencies": { "@alova/adapter-xhr": "^1.0.3", - "@babel/core": "^7.23.7", + "@babel/core": "^7.23.9", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.6", @@ -29,7 +31,7 @@ "@table-library/react-table-library": "4.1.7", "@types/imagemin": "^8.0.5", "@types/lodash-es": "^4.17.12", - "@types/node": "^20.11.5", + "@types/node": "^20.11.9", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", "@types/react-router-dom": "^5.3.3", diff --git a/interface/src/framework/system/UploadFileForm.tsx b/interface/src/framework/system/UploadFileForm.tsx index e0df328ed..490734b76 100644 --- a/interface/src/framework/system/UploadFileForm.tsx +++ b/interface/src/framework/system/UploadFileForm.tsx @@ -19,7 +19,7 @@ const UploadFileForm: FC = () => { const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), { immediate: false }); - const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), { + const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } = useRequest(EMSESP.getCustomizations(), { immediate: false }); const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), { @@ -80,7 +80,7 @@ const UploadFileForm: FC = () => { onSuccessGetSettings((event) => { saveFile(event.data, 'settings.json'); }); - onSuccessgetCustomizations((event) => { + onSuccessGetCustomizations((event) => { saveFile(event.data, 'customizations.json'); }); onSuccessGetEntities((event) => { diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index bc160d5b9..907ed4e2e 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -317,7 +317,7 @@ const de: Translation = { SCHEDULE_TIMER_2: 'jede Minute', SCHEDULE_TIMER_3: 'jede Stunde', CUSTOM_ENTITIES: 'Individuelle Entitäten', - ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus', + ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus', // TODO translate ENTITIES_UPDATED: 'Entitäten gespeichert', WRITEABLE: 'Schreibbar', SHOWING: 'Anzeigen von', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index d2978b1f3..f5748e7ba 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -318,7 +318,7 @@ const en: Translation = { SCHEDULE_TIMER_2: 'every minute', SCHEDULE_TIMER_3: 'every hour', CUSTOM_ENTITIES: 'Custom Entities', - ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', + ENTITIES_HELP_1: 'Define custom EMS entities or dynamic user variables', ENTITIES_UPDATED: 'Entities Updated', WRITEABLE: 'Writeable', SHOWING: 'Showing', diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index 622664da7..fccc3c6da 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -319,7 +319,7 @@ const it: Translation = { SCHEDULE_TIMER_2: 'Ogni minuto', SCHEDULE_TIMER_3: 'Ogni ora', CUSTOM_ENTITIES: 'Entità personalizzate', - ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS', + ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS', // TODO translate ENTITIES_UPDATED: 'Entità aggiornate', WRITEABLE: 'Scrivibile', SHOWING: 'Visualizza', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 1f9bf8d37..8978409dc 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -317,7 +317,7 @@ const nl: Translation = { SCHEDULE_TIMER_2: 'elke minuut', SCHEDULE_TIMER_3: 'elke huur', CUSTOM_ENTITIES: 'Aangepaste Entiteiten', - ENTITIES_HELP_1: 'Aangepaste entiteiten ophalen uit de EMS-bus', + ENTITIES_HELP_1: 'Aangepaste entiteiten ophalen uit de EMS-bus', // TODO translate ENTITIES_UPDATED: 'Entiteiten bijgewerkt', WRITEABLE: 'Beschrijfbare', SHOWING: 'Tonen', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 3bdc7cb0e..563022b67 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -317,20 +317,20 @@ const pl: BaseTranslation = { SCHEDULE_TIMER_2: 'co minutę', SCHEDULE_TIMER_3: 'co godzinę', CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}', - ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.', + ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.', // TODO translate ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.', WRITEABLE: 'Zapisywalna', SHOWING: 'Wyświetlane', SEARCH: 'Szukaj', CERT: 'Certyfikat główny TLS (pozostaw puste dla TLS-insecure)', ENABLE_TLS: 'Włącz wsparcie dla TLS', - ON: 'włączony', + ON: 'włączony', OFF: 'wyłączony', POLARITY: 'Typ przekaźnika', - ACTIVEHIGH: 'Wyzwalany stanem wysokim', - ACTIVELOW: 'Wyzwalany stanem niskim', - UNCHANGED: 'Zachowaj stan', - ALWAYS: 'Zawsze' + ACTIVEHIGH: 'Wyzwalany stanem wysokim', + ACTIVELOW: 'Wyzwalany stanem niskim', + UNCHANGED: 'Zachowaj stan', + ALWAYS: 'Zawsze' }; export default pl; diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index a7339e3c7..e6c405d59 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -316,7 +316,7 @@ const sk: Translation = { SCHEDULE_TIMER_2: 'každú minútu', SCHEDULE_TIMER_3: 'každú hodinu', CUSTOM_ENTITIES: 'Vlastné entity', - ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS', + ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS', // TODO translate ENTITIES_UPDATED: 'Aktualizované entity', WRITEABLE: 'Zapísateľný', SHOWING: 'Zobrazenie', diff --git a/interface/src/project/SettingsCustomEntities.tsx b/interface/src/project/SettingsCustomEntities.tsx index dfda883f0..534ecec07 100644 --- a/interface/src/project/SettingsCustomEntities.tsx +++ b/interface/src/project/SettingsCustomEntities.tsx @@ -1,7 +1,7 @@ import AddIcon from '@mui/icons-material/Add'; import CancelIcon from '@mui/icons-material/Cancel'; -import RefreshIcon from '@mui/icons-material/Refresh'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import RefreshIcon from '@mui/icons-material/Refresh'; import WarningIcon from '@mui/icons-material/Warning'; import { Button, Typography, Box } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; diff --git a/interface/src/project/SettingsCustomEntitiesDialog.tsx b/interface/src/project/SettingsCustomEntitiesDialog.tsx index 691f23b25..9c293dcd1 100644 --- a/interface/src/project/SettingsCustomEntitiesDialog.tsx +++ b/interface/src/project/SettingsCustomEntitiesDialog.tsx @@ -27,7 +27,7 @@ import { BlockFormControlLabel, ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { updateValue } from 'utils'; +import { numberValue, updateValue } from 'utils'; import { validate } from 'validators'; type SettingsCustomEntitiesDialogProps = { @@ -130,7 +130,7 @@ const SettingsCustomEntitiesDialog = ({ alovaInstance.Get(`/rest/coreData`); export const readDeviceData = (id: number) => alovaInstance.Get('/rest/deviceData', { // alovaInstance.Get(`/rest/deviceData/${id}`, { - params: { id }, // TODO replace later + params: { id }, // TODO replace later with id responseType: 'arraybuffer' // uses msgpack }); export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data); @@ -56,7 +56,7 @@ export const getSchedule = () => alovaInstance.Get('/rest/getSchedule'); export const readDeviceEntities = (id: number) => // alovaInstance.Get(`/rest/deviceEntities/${id}`, { alovaInstance.Get(`/rest/deviceEntities`, { - params: { id }, // TODO replace later + params: { id }, // TODO replace later with id responseType: 'arraybuffer', transformData(data: any) { return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index e2943a246..0e4a5091a 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -395,6 +395,7 @@ export const enum DeviceValueType { } export const DeviceValueTypeNames = [ + // 'BOOL', 'INT', 'UINT', diff --git a/lib_standalone/FSPersistence.h b/lib_standalone/FSPersistence.h index df37ef67f..1b8a68c49 100644 --- a/lib_standalone/FSPersistence.h +++ b/lib_standalone/FSPersistence.h @@ -18,9 +18,9 @@ class FSPersistence { } void readFromFS() { - Serial.println(); - Serial.print("Fake reading file "); - Serial.println(_filePath); + // Serial.println(); + // Serial.print("Fake reading file "); + // Serial.println(_filePath); applyDefaults(); } diff --git a/mock-api/Handler.ts b/mock-api/handler.ts similarity index 99% rename from mock-api/Handler.ts rename to mock-api/handler.ts index 08d26b876..87e02c773 100644 --- a/mock-api/Handler.ts +++ b/mock-api/handler.ts @@ -1,6 +1,6 @@ import { Router } from 'itty-router'; import { Encoder } from '@msgpack/msgpack'; -import busboy from 'busboy'; +// import busboy from 'busboy'; // import multer from 'multer'; const encoder = new Encoder(); diff --git a/mock-api/package.json b/mock-api/package.json index de5e4f285..0397efbe9 100644 --- a/mock-api/package.json +++ b/mock-api/package.json @@ -6,12 +6,15 @@ "main": "server.ts", "license": "MIT", "scripts": { - "standalone": "bun --watch server.ts" + "standalone": "bun --watch server.ts", + "old_standalone": "node server.js" }, "dependencies": { "@msgpack/msgpack": "^2.8.0", - "busboy": "^1.6.0", - "itty-router": "^4.0.27" + "compression": "^1.7.4", + "express": "^4.18.2", + "itty-router": "^4.0.27", + "multer": "^1.4.5-lts.1" }, "packageManager": "yarn@4.0.2", "devDependencies": { diff --git a/mock-api/server_notused.js b/mock-api/server.js similarity index 99% rename from mock-api/server_notused.js rename to mock-api/server.js index 88e478349..ba672486f 100644 --- a/mock-api/server_notused.js +++ b/mock-api/server.js @@ -14,7 +14,9 @@ rest_server.use(express.json()); // uploads const upload = multer({ dest: '../mock-api/uploads' }); + function progress_middleware(req, res, next) { + console.log('Uploading file... '); let progress = 0; const file_size = req.headers['content-length']; @@ -24,7 +26,7 @@ function progress_middleware(req, res, next) { const percentage = (progress / file_size) * 100; console.log(`Progress: ${Math.round(percentage)}%`); // await delay(1000); // slow it down - delay_blocking(200); // slow it down + delay_blocking(1000); // slow it down }); next(); // invoke next middleware which is multer } @@ -33,7 +35,8 @@ function progress_middleware(req, res, next) { const delay = (ms) => new Promise((res) => setTimeout(res, ms)); function delay_blocking(milliseconds) { var start = new Date().getTime(); - for (var i = 0; i < 1e7; i++) { + // for (var i = 0; i < 1e7; i++) { + while (true) { if (new Date().getTime() - start > milliseconds) { break; } @@ -2758,7 +2761,7 @@ rest_server.get(ENTITIES_ENDPOINT, (req, res) => { // start server const expressServer = rest_server.listen(port, () => - console.log(`EMS-ESP REST API server running on http://localhost:${port}/`) + console.log(`Legacy EMS-ESP REST API server running on http://localhost:${port}/`) ); // event source @@ -2793,5 +2796,5 @@ rest_server.get(ES_LOG_ENDPOINT, function (req, res) { log_index = 0; } fetch_log.events.push(data); // append to buffer - }, 300); + }, 5000); }); diff --git a/mock-api/server.ts b/mock-api/server.ts index 612e7a9ab..7056e5611 100644 --- a/mock-api/server.ts +++ b/mock-api/server.ts @@ -1,4 +1,4 @@ -import { handleRequest } from './Handler'; +import { handleRequest } from './handler'; export default { port: 3080, diff --git a/mock-api/yarn.lock b/mock-api/yarn.lock index 8d3e1278c..7b08cbae3 100644 --- a/mock-api/yarn.lock +++ b/mock-api/yarn.lock @@ -122,18 +122,71 @@ __metadata: languageName: node linkType: hard +"accepts@npm:~1.3.5, accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 67eaaa90e2917c58418e7a9b89392002d2b1ccd69bcca4799135d0c632f3b082f23f4ae4ddeedbced5aa59bcc7bdf4699c69ebed4593696c922462b7bc5744d6 + languageName: node + linkType: hard + "api@workspace:.": version: 0.0.0-use.local resolution: "api@workspace:." dependencies: "@msgpack/msgpack": "npm:^2.8.0" "@types/multer": "npm:^1.4.11" - busboy: "npm:^1.6.0" + compression: "npm:^1.7.4" + express: "npm:^4.18.2" itty-router: "npm:^4.0.27" + multer: "npm:^1.4.5-lts.1" languageName: unknown linkType: soft -"busboy@npm:^1.6.0": +"append-field@npm:^1.0.0": + version: 1.0.0 + resolution: "append-field@npm:1.0.0" + checksum: afb50f5ff668af1cb66bc5cfebb55ed9a1d99e24901782ee83d00aed1a499835f9375a149cf27b17f79595ecfcc3d1de0cd5b020b210a5359c43eaf607c217de + languageName: node + linkType: hard + +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: e13c9d247241be82f8b4ec71d035ed7204baa82fae820d4db6948d30d3c4a9f2b3905eb2eec2b937d4aa3565200bd3a1c500480114cff649fa748747d2a50feb + languageName: node + linkType: hard + +"body-parser@npm:1.20.1": + version: 1.20.1 + resolution: "body-parser@npm:1.20.1" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.4" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.11.0" + raw-body: "npm:2.5.1" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 5f8d128022a2fb8b6e7990d30878a0182f300b70e46b3f9d358a9433ad6275f0de46add6d63206da3637c01c3b38b6111a7480f7e7ac2e9f7b989f6133fe5510 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb + languageName: node + linkType: hard + +"busboy@npm:^1.0.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" dependencies: @@ -142,6 +195,337 @@ __metadata: languageName: node linkType: hard +"bytes@npm:3.0.0": + version: 3.0.0 + resolution: "bytes@npm:3.0.0" + checksum: a2b386dd8188849a5325f58eef69c3b73c51801c08ffc6963eddc9be244089ba32d19347caf6d145c86f315ae1b1fc7061a32b0c1aa6379e6a719090287ed101 + languageName: node + linkType: hard + +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.0": + version: 1.0.5 + resolution: "call-bind@npm:1.0.5" + dependencies: + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.1" + set-function-length: "npm:^1.1.1" + checksum: 246d44db6ef9bbd418828dbd5337f80b46be4398d522eded015f31554cbb2ea33025b0203b75c7ab05a1a255b56ef218880cca1743e4121e306729f9e414da39 + languageName: node + linkType: hard + +"compressible@npm:~2.0.16": + version: 2.0.18 + resolution: "compressible@npm:2.0.18" + dependencies: + mime-db: "npm:>= 1.43.0 < 2" + checksum: 58321a85b375d39230405654721353f709d0c1442129e9a17081771b816302a012471a9b8f4864c7dbe02eef7f2aaac3c614795197092262e94b409c9be108f0 + languageName: node + linkType: hard + +"compression@npm:^1.7.4": + version: 1.7.4 + resolution: "compression@npm:1.7.4" + dependencies: + accepts: "npm:~1.3.5" + bytes: "npm:3.0.0" + compressible: "npm:~2.0.16" + debug: "npm:2.6.9" + on-headers: "npm:~1.0.2" + safe-buffer: "npm:5.1.2" + vary: "npm:~1.1.2" + checksum: 469cd097908fe1d3ff146596d4c24216ad25eabb565c5456660bdcb3a14c82ebc45c23ce56e19fc642746cf407093b55ab9aa1ac30b06883b27c6c736e6383c2 + languageName: node + linkType: hard + +"concat-stream@npm:^1.5.2": + version: 1.6.2 + resolution: "concat-stream@npm:1.6.2" + dependencies: + buffer-from: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^2.2.2" + typedarray: "npm:^0.0.6" + checksum: 71db903c84fc073ca35a274074e8d26c4330713d299f8623e993c448c1f6bf8b967806dd1d1a7b0f8add6f15ab1af7435df21fe79b4fe7efd78420c89e054e28 + languageName: node + linkType: hard + +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: b7f4ce176e324f19324be69b05bf6f6e411160ac94bc523b782248129eb1ef3be006f6cff431aaea5e337fe5d176ce8830b8c2a1b721626ead8933f0cbe78720 + languageName: node + linkType: hard + +"content-type@npm:~1.0.4": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 + languageName: node + linkType: hard + +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a + languageName: node + linkType: hard + +"cookie@npm:0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: aae7911ddc5f444a9025fbd979ad1b5d60191011339bce48e555cb83343d0f98b865ff5c4d71fecdfb8555a5cafdc65632f6fce172f32aaf6936830a883a0380 + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 + languageName: node + linkType: hard + +"debug@npm:2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: e07005f2b40e04f1bd14a3dd20520e9c4f25f60224cb006ce9d6781732c917964e9ec029fc7f1a151083cd929025ad5133814d4dc624a9aaf020effe4914ed14 + languageName: node + linkType: hard + +"define-data-property@npm:^1.1.1": + version: 1.1.1 + resolution: "define-data-property@npm:1.1.1" + dependencies: + get-intrinsic: "npm:^1.2.1" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + checksum: 5573c8df96b5857408cad64d9b91b69152e305ce4b06218e5f49b59c6cafdbb90a8bd8a0bb83c7bc67a8d479c04aa697063c9bc28d849b7282f9327586d6bc7b + languageName: node + linkType: hard + +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + +"express@npm:^4.18.2": + version: 4.18.2 + resolution: "express@npm:4.18.2" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.1" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.5.0" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.2.0" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.1" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.7" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.11.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.18.0" + serve-static: "npm:1.15.0" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 869ae89ed6ff4bed7b373079dc58e5dddcf2915a2669b36037ff78c99d675ae930e5fe052b35c24f56557d28a023bb1cbe3e2f2fb87eaab96a1cedd7e597809d + languageName: node + linkType: hard + +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: 635718cb203c6d18e6b48dfbb6c54ccb08ea470e4f474ddcef38c47edcf3227feec316f886dd701235997d8af35240cae49856721ce18f539ad038665ebbf163 + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 29ba9fd347117144e97cbb8852baae5e8b2acb7d1b591ef85695ed96f5b933b1804a7fac4a15dd09ca7ac7d0cdc104410e8102aae2dd3faa570a797ba07adb81 + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2": + version: 1.2.2 + resolution: "get-intrinsic@npm:1.2.2" + dependencies: + function-bind: "npm:^1.1.2" + has-proto: "npm:^1.0.1" + has-symbols: "npm:^1.0.3" + hasown: "npm:^2.0.0" + checksum: aa96db4f809734d26d49b59bc8669d73a0ae792da561514e987735573a1dfaede516cd102f217a078ea2b42d4c4fb1f83d487932cb15d49826b726cc9cd4470b + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.1.3" + checksum: 5fbc7ad57b368ae4cd2f41214bd947b045c1a4be2f194a7be1778d71f8af9dbf4004221f3b6f23e30820eb0d052b4f819fe6ebe8221e2a3c6f0ee4ef173421ca + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.1": + version: 1.0.1 + resolution: "has-property-descriptors@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.2" + checksum: 21a47bb080a24e79594aef1ce71e1a18a1c5ab4120308e218088f67ebb7f6f408847541e2d96e5bd00e90eef5c5a49e4ebbdc8fc2d5b365a2c379aef071642f0 + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: eab2ab0ed1eae6d058b9bbc4c1d99d2751b29717be80d02fd03ead8b62675488de0c7359bc1fdd4b87ef6fd11e796a9631ad4d7452d9324fdada70158c2e5be7 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: 464f97a8202a7690dadd026e6d73b1ceeddd60fe6acfd06151106f050303eaa75855aaa94969df8015c11ff7c505f196114d22f7386b4a471038da5874cf5e9b + languageName: node + linkType: hard + +"hasown@npm:^2.0.0": + version: 2.0.0 + resolution: "hasown@npm:2.0.0" + dependencies: + function-bind: "npm:^1.1.2" + checksum: c330f8d93f9d23fe632c719d4db3d698ef7d7c367d51548b836069e06a90fa9151e868c8e67353cfe98d67865bf7354855db28fa36eb1b18fa5d4a3f4e7f1c90 + languageName: node + linkType: hard + +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 0e7f76ee8ff8a33e58a3281a469815b893c41357378f408be8f6d4aa7d1efafb0da064625518e7078381b6a92325949b119dc38fcb30bdbc4e3a35f78c44c439 + languageName: node + linkType: hard + +"iconv-lite@npm:0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 + languageName: node + linkType: hard + +"inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 864d0cced0c0832700e9621913a6429ccdc67f37c1bd78fb8c6789fff35c9d167cb329134acad2290497a53336813ab4798d2794fd675d5eb33b5fdf0982b9ca + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab + languageName: node + linkType: hard + "itty-router@npm:^4.0.27": version: 4.0.27 resolution: "itty-router@npm:4.0.27" @@ -149,6 +533,302 @@ __metadata: languageName: node linkType: hard +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 38e0984db39139604756903a01397e29e17dcb04207bb3e081412ce725ab17338ecc47220c1b186b6bbe79a658aad1b0d41142884f5a481f36290cdefbe6aa46 + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.1": + version: 1.0.1 + resolution: "merge-descriptors@npm:1.0.1" + checksum: 5abc259d2ae25bb06d19ce2b94a21632583c74e2a9109ee1ba7fd147aa7362b380d971e0251069f8b3eb7d48c21ac839e21fa177b335e82c76ec172e30c31a26 + languageName: node + linkType: hard + +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: a385dd974faa34b5dd021b2bbf78c722881bf6f003bfe6d391d7da3ea1ed625d1ff10ddd13c57531f628b3e785be38d3eed10ad03cebd90b76932413df9a1820 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 + languageName: node + linkType: hard + +"mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a + languageName: node + linkType: hard + +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: b7d98bb1e006c0e63e2c91b590fe1163b872abf8f7ef224d53dd31499c2197278a6d3d0864c45239b1a93d22feaf6f9477e9fc847eef945838150b8c02d03170 + languageName: node + linkType: hard + +"minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f + languageName: node + linkType: hard + +"mkdirp@npm:^0.5.4": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: "npm:^1.2.6" + bin: + mkdirp: bin/cmd.js + checksum: 0c91b721bb12c3f9af4b77ebf73604baf350e64d80df91754dc509491ae93bf238581e59c7188360cec7cb62fc4100959245a42cfe01834efedc5e9d068376c2 + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 0e6a22b8b746d2e0b65a430519934fefd41b6db0682e3477c10f60c76e947c4c0ad06f63ffdf1d78d335f83edee8c0aa928aa66a36c7cd95b69b26f468d527f4 + languageName: node + linkType: hard + +"ms@npm:2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + +"multer@npm:^1.4.5-lts.1": + version: 1.4.5-lts.1 + resolution: "multer@npm:1.4.5-lts.1" + dependencies: + append-field: "npm:^1.0.0" + busboy: "npm:^1.0.0" + concat-stream: "npm:^1.5.2" + mkdirp: "npm:^0.5.4" + object-assign: "npm:^4.1.1" + type-is: "npm:^1.6.4" + xtend: "npm:^4.0.0" + checksum: 957c09956f3b7f79d8586cac5e2a50e9a5c3011eb841667b5e4590c5f31d9464f5b46aecd399c83e183a15b88b019cccf0e4fa5620db40bf16b9e3af7fab3ac6 + languageName: node + linkType: hard + +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 2723fb822a17ad55c93a588a4bc44d53b22855bf4be5499916ca0cab1e7165409d0b288ba2577d7b029f10ce18cf2ed8e703e5af31c984e1e2304277ef979837 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f + languageName: node + linkType: hard + +"object-inspect@npm:^1.9.0": + version: 1.13.1 + resolution: "object-inspect@npm:1.13.1" + checksum: 92f4989ed83422d56431bc39656d4c780348eb15d397ce352ade6b7fec08f973b53744bd41b94af021901e61acaf78fcc19e65bf464ecc0df958586a672700f0 + languageName: node + linkType: hard + +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 8e81472c5028125c8c39044ac4ab8ba51a7cdc19a9fbd4710f5d524a74c6d8c9ded4dd0eed83f28d3d33ac1d7a6a439ba948ccb765ac6ce87f30450a26bfe2ea + languageName: node + linkType: hard + +"on-headers@npm:~1.0.2": + version: 1.0.2 + resolution: "on-headers@npm:1.0.2" + checksum: 870766c16345855e2012e9422ba1ab110c7e44ad5891a67790f84610bd70a72b67fdd71baf497295f1d1bf38dd4c92248f825d48729c53c0eae5262fb69fa171 + languageName: node + linkType: hard + +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 + languageName: node + linkType: hard + +"path-to-regexp@npm:0.1.7": + version: 0.1.7 + resolution: "path-to-regexp@npm:0.1.7" + checksum: 701c99e1f08e3400bea4d701cf6f03517474bb1b608da71c78b1eb261415b645c5670dfae49808c89e12cea2dccd113b069f040a80de012da0400191c6dbd1c8 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf + languageName: node + linkType: hard + +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: f24a0c80af0e75d31e3451398670d73406ec642914da11a2965b80b1898ca6f66a0e3e091a11a4327079b2b268795f6fa06691923fef91887215c3d0e8ea3f68 + languageName: node + linkType: hard + +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: "npm:^1.0.4" + checksum: 5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e + languageName: node + linkType: hard + +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: ce21ef2a2dd40506893157970dc76e835c78cf56437e26e19189c48d5291e7279314477b06ac38abd6a401b661a6840f7b03bd0b1249da9b691deeaa15872c26 + languageName: node + linkType: hard + +"raw-body@npm:2.5.1": + version: 2.5.1 + resolution: "raw-body@npm:2.5.1" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 280bedc12db3490ecd06f740bdcf66093a07535374b51331242382c0e130bb273ebb611b7bc4cba1b4b4e016cc7b1f4b05a6df885a6af39c2bc3b94c02291c84 + languageName: node + linkType: hard + +"readable-stream@npm:^2.2.2": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 8500dd3a90e391d6c5d889256d50ec6026c059fadee98ae9aa9b86757d60ac46fff24fafb7a39fa41d54cb39d8be56cc77be202ebd4cd8ffcf4cb226cbaa40d4 + languageName: node + linkType: hard + +"safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 + languageName: node + linkType: hard + +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: ec66c0ad109680ad8141d507677cfd8b4e40b9559de23191871803ed241718e99026faa46c398dcfb9250676076573bd6bfe5d0ec347f88f4b7b8533d1d391cb + languageName: node + linkType: hard + +"serve-static@npm:1.15.0": + version: 1.15.0 + resolution: "serve-static@npm:1.15.0" + dependencies: + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.18.0" + checksum: 699b2d4c29807a51d9b5e0f24955346911437aebb0178b3c4833ad30d3eca93385ff9927254f5c16da345903cad39d9cd4a532198c95a5129cc4ed43911b15a4 + languageName: node + linkType: hard + +"set-function-length@npm:^1.1.1": + version: 1.2.0 + resolution: "set-function-length@npm:1.2.0" + dependencies: + define-data-property: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.2" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.1" + checksum: 6d609cd060c488d7d2178a5d4c3689f8a6afa26fa4c48ff4a0516664ff9b84c1c0898915777f5628092dab55c4fcead205525e2edd15c659423bf86f790fdcae + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4": + version: 1.0.4 + resolution: "side-channel@npm:1.0.4" + dependencies: + call-bind: "npm:^1.0.0" + get-intrinsic: "npm:^1.0.2" + object-inspect: "npm:^1.9.0" + checksum: c4998d9fc530b0e75a7fd791ad868fdc42846f072734f9080ff55cc8dc7d3899abcda24fd896aa6648c3ab7021b4bb478073eb4f44dfd55bce9714bc1a7c5d45 + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb + languageName: node + linkType: hard + "streamsearch@npm:^1.1.0": version: 1.1.0 resolution: "streamsearch@npm:1.1.0" @@ -156,9 +836,77 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 7c41c17ed4dea105231f6df208002ebddd732e8e9e2d619d133cecd8e0087ddfd9587d2feb3c8caf3213cbd841ada6d057f5142cae68a4e62d3540778d9819b4 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 + languageName: node + linkType: hard + +"type-is@npm:^1.6.4, type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 0bd9eeae5efd27d98fd63519f999908c009e148039d8e7179a074f105362d4fcc214c38b24f6cda79c87e563cbd12083a4691381ed28559220d4a10c2047bed4 + languageName: node + linkType: hard + +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 2cc1bcf7d8c1237f6a16c04efc06637b2c5f2d74e58e84665445cf87668b85a21ab18dd751fa49eee6ae024b70326635d7b79ad37b1c370ed2fec6aeeeb52714 + languageName: node + linkType: hard + "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5" checksum: 0097779d94bc0fd26f0418b3a05472410408877279141ded2bd449167be1aed7ea5b76f756562cb3586a07f251b90799bab22d9019ceba49c037c76445f7cddd languageName: node linkType: hard + +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 + languageName: node + linkType: hard + +"util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 + languageName: node + linkType: hard + +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 5d6949693d58cb2e636a84f3ee1c6e7b2f9c16cb1d42d0ecb386d8c025c69e327205aa1c69e2868cc06a01e5e20681fbba55a4e0ed0cce913d60334024eae798 + languageName: node + linkType: hard + +"vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 + languageName: node + linkType: hard + +"xtend@npm:^4.0.0": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a + languageName: node + linkType: hard diff --git a/platformio.ini b/platformio.ini index 0b38c7e00..fbb56daed 100644 --- a/platformio.ini +++ b/platformio.ini @@ -171,7 +171,7 @@ platform = native build_flags = -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ - -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.4-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" + -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.5-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" -lpthread -std=gnu++11 -Og -ggdb build_src_flags = diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index fce90371d..46794b512 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -33,29 +33,13 @@ void AnalogSensor::start() { LOG_INFO("Starting Analog sensor service"); - // Add API call for /info - Command::add( - EMSdevice::DeviceType::ANALOGSENSOR, - F_(info), - [&](const char * value, const int8_t id, JsonObject output) { return command_info(value, id, output); }, - FL_(info_cmd)); - Command::add( - EMSdevice::DeviceType::ANALOGSENSOR, - F_(values), - [&](const char * value, const int8_t id, JsonObject output) { return command_info(value, 0, output); }, - nullptr, - CommandFlag::HIDDEN); // this command is hidden + // Add API calls Command::add( EMSdevice::DeviceType::ANALOGSENSOR, F_(setvalue), [&](const char * value, const int8_t id) { return command_setvalue(value, id); }, FL_(setiovalue_cmd), CommandFlag::ADMIN_ONLY); - Command::add( - EMSdevice::DeviceType::ANALOGSENSOR, - F_(commands), - [&](const char * value, const int8_t id, JsonObject output) { return command_commands(value, id, output); }, - FL_(commands_cmd)); char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "%s/#", F_(analogsensor)); @@ -333,10 +317,10 @@ void AnalogSensor::loop() { } // update analog information name and offset -// a type of -1 is used to delete the sensor +// a type value of -1 is used to delete the sensor bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted) { - boolean found_sensor = false; // see if we can find the sensor in our customization list - + // first see if we can find the sensor in our customization list + bool found_sensor = false; EMSESP::webCustomizationService.update( [&](WebCustomization & settings) { for (auto & AnalogCustomization : settings.analogCustomizations) { @@ -374,7 +358,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset, remove_ha_topic(type, gpio); // the GPIO } - // we didn't find it, it's new, so create and store it + // we didn't find it, it's new, so create and store it in the customization list if (!found_sensor) { EMSESP::webCustomizationService.update( [&](WebCustomization & settings) { @@ -636,46 +620,64 @@ void AnalogSensor::publish_values(const bool force) { Mqtt::queue_publish(topic, doc.as()); } -// called from emsesp.cpp, similar to the emsdevice->get_value_info -// searches by name -bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int8_t id) const { +// called from emsesp.cpp for commands +// searches sensor by name +bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int8_t id) { if (sensors_.empty()) { + return true; // no sensors, return true + } + + uint8_t show_all = 0; + if (Helpers::hasValue(cmd)) { + show_all = (strncmp(cmd, F_(info), 4) == 0) ? 1 : (strncmp(cmd, F_(values), 6) == 0) ? 2 : 0; + } + + // see if we're showing all sensors + if (show_all) { + for (const auto & sensor : sensors_) { + if (show_all == 1) { + // info + JsonObject dataSensor = output[sensor.name()].to(); + addSensorJson(dataSensor, sensor); + } else { + // values, shortname version. Also used in 'system allvalues' + output[sensor.name()] = sensor.value(); + } + } return true; } - // make a copy of the string command for parsing - char command_s[30]; - strlcpy(command_s, cmd, sizeof(command_s)); - char * attribute_s = nullptr; + + // check of it a 'commmands' command + if (Helpers::toLower(cmd) == F_(commands)) { + return Command::list(EMSdevice::DeviceType::TEMPERATURESENSOR, output); + } + + // this is for a specific sensor + // make a copy of the string command for parsing, and lowercase it + char sensor_name[30] = {'\0'}; + char * attribute_s = nullptr; + strlcpy(sensor_name, cmd, sizeof(sensor_name)); + auto sensor_lowercase = Helpers::toLower(sensor_name); // check specific attribute to fetch instead of the complete record - char * breakp = strchr(command_s, '/'); + char * breakp = strchr(sensor_name, '/'); if (breakp) { *breakp = '\0'; attribute_s = breakp + 1; } for (const auto & sensor : sensors_) { - if (Helpers::toLower(command_s) == Helpers::toLower(sensor.name().c_str()) || Helpers::atoint(command_s) == sensor.gpio()) { - output["gpio"] = sensor.gpio(); - output["name"] = sensor.name(); - output["type"] = F_(number); - output["analog"] = FL_(list_sensortype)[sensor.type()]; - output["uom"] = EMSdevice::uom_to_string(sensor.uom()); - output["offset"] = sensor.offset(); - output["factor"] = sensor.factor(); - output["value"] = sensor.value(); - output["writeable"] = sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2); - // min/max for writeable analogs - if (sensor.type() == AnalogType::COUNTER) { - output["min"] = 0; - output["max"] = 4000000; - } else if (sensor.type() == AnalogType::DIGITAL_OUT) { - output["min"] = 0; - output["max"] = sensor.gpio() == 25 || sensor.gpio() == 26 ? 255 : 1; - } else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) { - output["min"] = 0; - output["max"] = 100; - } + if (sensor_lowercase == Helpers::toLower(sensor.name().c_str()) || Helpers::atoint(sensor_name) == sensor.gpio()) { + // add the details + addSensorJson(output, sensor); + + /* + // if someone wants gpio numbers + char gpio_str[9]; + snprintf(gpio_str, sizeof(gpio_str), "gpio_%02d", sensor.gpio()); + output[gpio_str] = sensor.value(); + */ + // if we're filtering on an attribute, go find it if (attribute_s) { if (output.containsKey(attribute_s)) { @@ -685,56 +687,45 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int return true; } else { char error[100]; - snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s); + snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, sensor_name); output.clear(); output["message"] = error; return false; } } - return true; + return true; // found a match, exit } } - return false; + return false; // not found } -// creates JSON doc from values -// returns true if there are no sensors -bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject output) const { - if (sensors_.empty()) { - return true; +void AnalogSensor::addSensorJson(JsonObject output, const Sensor & sensor) { + output["gpio"] = sensor.gpio(); + output["type"] = F_(number); + output["analog"] = FL_(list_sensortype)[sensor.type()]; + output["value"] = sensor.value(); + output["writeable"] = sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2); + if (sensor.type() == AnalogType::COUNTER) { + output["min"] = 0; + output["max"] = 4000000; + output["start_value"] = sensor.offset(); + output["factor"] = sensor.factor(); + output["uom"] = EMSdevice::uom_to_string(sensor.uom()); + } else if (sensor.type() == AnalogType::ADC) { + output["offset"] = sensor.offset(); + output["factor"] = sensor.factor(); + output["uom"] = EMSdevice::uom_to_string(sensor.uom()); + } else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) { + output["factor"] = sensor.factor(); + } else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) { + output["frequency"] = sensor.factor(); + output["min"] = 0; + output["max"] = 100; + output["uom"] = EMSdevice::uom_to_string(sensor.uom()); + } else if (sensor.type() == AnalogType::DIGITAL_OUT) { + output["min"] = 0; + output["max"] = sensor.gpio() == 25 || sensor.gpio() == 26 ? 255 : 1; } - - for (const auto & sensor : sensors_) { - if (id == -1) { // show number and id for info command - JsonObject dataSensor = output[sensor.name()].to(); - dataSensor["gpio"] = sensor.gpio(); - dataSensor["type"] = F_(number); - dataSensor["value"] = sensor.value(); - dataSensor["analog"] = FL_(list_sensortype)[sensor.type()]; - if (sensor.type() == AnalogType::ADC) { - dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom()); - dataSensor["offset"] = sensor.offset(); - dataSensor["factor"] = sensor.factor(); - } else if (sensor.type() == AnalogType::COUNTER) { - dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom()); - dataSensor["start_value"] = sensor.offset(); - dataSensor["factor"] = sensor.factor(); - } else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) { - dataSensor["factor"] = sensor.factor(); - } else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) { - dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom()); - dataSensor["frequency"] = sensor.factor(); - } - } else if (id == 0) { // output values command - output[sensor.name()] = sensor.value(); - } else { // if someone wants gpio numbers - char gpio_str[9]; - snprintf(gpio_str, sizeof(gpio_str), "gpio_%02d", sensor.gpio()); - output[gpio_str] = sensor.value(); - } - } - - return (output.size() > 0); } // this creates the sensor, initializing everything @@ -850,20 +841,4 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) { return false; } -// list commands -bool AnalogSensor::command_commands(const char * value, const int8_t id, JsonObject output) { - return Command::list(EMSdevice::DeviceType::ANALOGSENSOR, output); -} - -// hard coded tests -#ifdef EMSESP_TEST -void AnalogSensor::test() { - sensors_.emplace_back(36, "test12", 0, 0.1, 17, AnalogType::ADC); - sensors_.back().set_value(12.4); - - sensors_.emplace_back(37, "test13", 0, 0, 0, AnalogType::DIGITAL_IN); - sensors_.back().set_value(13); -} -#endif - } // namespace emsesp \ No newline at end of file diff --git a/src/analogsensor.h b/src/analogsensor.h index 12615298d..8681106a4 100644 --- a/src/analogsensor.h +++ b/src/analogsensor.h @@ -153,16 +153,9 @@ class AnalogSensor { } bool update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false); - bool get_value_info(JsonObject output, const char * cmd, const int8_t id) const; + bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1); void store_counters(); - bool command_info(const char * value, const int8_t id, JsonObject output) const; - - -#if defined(EMSESP_TEST) - void test(); -#endif - private: static constexpr uint8_t MAX_SENSORS = 20; static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500; @@ -172,7 +165,7 @@ class AnalogSensor { void remove_ha_topic(const int8_t type, const uint8_t id) const; bool command_setvalue(const char * value, const int8_t gpio); void measure(); - bool command_commands(const char * value, const int8_t id, JsonObject output); + void addSensorJson(JsonObject output, const Sensor & sensor); std::vector sensors_; // our list of sensors diff --git a/src/command.cpp b/src/command.cpp index b80fd0ee4..12b4e13a7 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -304,6 +304,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * // check if its a call to an end-point of a device // this is used to fetch the attributes of the device entity, or call a command directly + // for example info, values, commands, etc bool single_command = (!value || !strlen(value)); if (single_command) { // exception 1: anything that is from System @@ -365,10 +366,10 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * // report back. If not OK show output from error, other return the HTTP code if (return_code != CommandRet::OK) { - if (value == nullptr) { + if ((value == nullptr) || (strlen(value) == 0)) { LOG_ERROR("Command '%s' failed with code: %d", cmd, return_code); } else { - LOG_ERROR("Command '%s:%s' failed with code: %d", cmd, value, return_code); + LOG_ERROR("Command '%s/%s' failed with code: %d", cmd, value, return_code); } return message(return_code, "callback function failed", output); } @@ -443,7 +444,7 @@ bool Command::list(const uint8_t device_type, JsonObject output) { return false; } - // create a list of commands, sort them + // create a list of commands we have registered, and sort them std::list sorted_cmds; for (const auto & cf : cmdfunctions_) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { @@ -452,6 +453,12 @@ bool Command::list(const uint8_t device_type, JsonObject output) { } sorted_cmds.sort(); + // force add info and commands for those non-EMS devices + if (device_type == EMSdevice::DeviceType::TEMPERATURESENSOR) { + output[F_(info)] = Helpers::translated_word(FL_(info_cmd)); + output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd)); + } + for (const auto & cl : sorted_cmds) { for (const auto & cf : cmdfunctions_) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == std::string(cf.cmd_))) { @@ -476,16 +483,26 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo return; } - // create a list of commands, sort them + // create list of commands we have registered std::list sorted_cmds; for (const auto & cf : cmdfunctions_) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { sorted_cmds.push_back((cf.cmd_)); } } - sorted_cmds.sort(); - // if not in verbose mode, just print them on a single line + // non EMS devices always have an info and commands command + bool show_info = (device_type == EMSdevice::DeviceType::TEMPERATURESENSOR || device_type == EMSdevice::DeviceType::ANALOGSENSOR + || device_type == EMSdevice::DeviceType::SCHEDULER || device_type == EMSdevice::DeviceType::CUSTOM); + + if (!verbose && show_info) { + sorted_cmds.push_back(F_(info)); + sorted_cmds.push_back(F_(commands)); + } + + sorted_cmds.sort(); // sort them + + // if not in verbose mode, just print them on a single line and exit if (!verbose) { for (const auto & cl : sorted_cmds) { shell.print(cl); @@ -496,7 +513,16 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo } // verbose mode - shell.println(); + shell.printfln("\n%s%s %s:%s", COLOR_BOLD_ON, COLOR_YELLOW, EMSdevice::device_type_2_device_name(device_type), COLOR_RESET); + + // we hard code 'info' and 'commmands' commands so print them first + if (show_info) { + shell.printf(" info:\t\t\t\t%slists all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); + shell.println(COLOR_RESET); + shell.printf(" commands:\t\t\t%slists all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); + shell.println(COLOR_RESET); + } + for (const auto & cl : sorted_cmds) { // find and print the description for (const auto & cf : cmdfunctions_) { @@ -531,8 +557,6 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo } shell.println(); } - - shell.println(); } // see if a device_type is active and has associated commands @@ -600,67 +624,32 @@ void Command::show_devices(uuid::console::Shell & shell) { shell.println(); } -// output list of all commands to console +// 'show commmands' : output list of all commands to console // calls show with verbose mode set void Command::show_all(uuid::console::Shell & shell) { shell.printfln("Showing all available commands (%s*%s=authentication not required):", COLOR_BRIGHT_GREEN, COLOR_RESET); - // show system first - shell.print(COLOR_BOLD_ON); - shell.print(COLOR_YELLOW); - shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM)); - shell.print(COLOR_RESET); + // show system ones first show(shell, EMSdevice::DeviceType::SYSTEM, true); - - // show Custom - shell.print(COLOR_BOLD_ON); - shell.print(COLOR_YELLOW); - shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::CUSTOM)); - shell.println(COLOR_RESET); - shell.printf(" info:\t\t\t\t%slists all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); - shell.println(COLOR_RESET); - shell.printf(" commands:\t\t\t%slists all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); - shell.print(COLOR_RESET); show(shell, EMSdevice::DeviceType::CUSTOM, true); - - // show scheduler - shell.print(COLOR_BOLD_ON); - shell.print(COLOR_YELLOW); - shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SCHEDULER)); - shell.println(COLOR_RESET); - shell.printf(" info:\t\t\t\t%slists all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); - shell.println(COLOR_RESET); - shell.printf(" commands:\t\t\t%slists all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); - shell.print(COLOR_RESET); show(shell, EMSdevice::DeviceType::SCHEDULER, true); - // show sensors + // then sensors if (EMSESP::sensor_enabled()) { - shell.print(COLOR_BOLD_ON); - shell.print(COLOR_YELLOW); - shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::TEMPERATURESENSOR)); - shell.print(COLOR_RESET); show(shell, EMSdevice::DeviceType::TEMPERATURESENSOR, true); } - if (EMSESP::analog_enabled()) { - shell.print(COLOR_BOLD_ON); - shell.print(COLOR_YELLOW); - shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR)); - shell.print(COLOR_RESET); show(shell, EMSdevice::DeviceType::ANALOGSENSOR, true); } - // do this in the order of factory classes to keep a consistent order when displaying + // now EMS devices, do this in the order of factory classes to keep a consistent order when displaying for (const auto & device_class : EMSFactory::device_handlers()) { if (Command::device_has_commands(device_class.first)) { - shell.print(COLOR_BOLD_ON); - shell.print(COLOR_YELLOW); - shell.printf(" %s: ", EMSdevice::device_type_2_device_name(device_class.first)); - shell.print(COLOR_RESET); show(shell, device_class.first, true); } } + + shell.println(); } // Extract only the path component from the passed URI and normalized it diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index f492d0593..720a812af 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -946,7 +946,7 @@ void EMSdevice::generate_values_web(JsonObject output) { // add name, prefixing the tag if it exists. This is the id used in the WebUI table and must be unique obj["id"] = dv.has_tag() ? mask + tag_to_string(dv.tag) + " " + fullname : mask + fullname; // suffix tag - // TODO check TAG https://github.com/emsesp/EMS-ESP32/issues/1338 + // TAG https://github.com/emsesp/EMS-ESP32/issues/1338 // obj["id"] = dv.has_tag() ? mask + fullname + " " + tag_to_string(dv.tag) : mask + fullname; // suffix tag // add commands and options @@ -1060,7 +1060,7 @@ void EMSdevice::generate_values_web_customization(JsonArray output) { if (fullname) { obj["n"] = dv.has_tag() ? std::string(tag_to_string(dv.tag)) + " " + fullname : fullname; // prefix tag - // TODO check TAG https://github.com/emsesp/EMS-ESP32/issues/1338 + // TAG https://github.com/emsesp/EMS-ESP32/issues/1338 // obj["n"] = (dv.has_tag()) ? fullname + " " + tag_to_string(dv.tag) : fullname; // suffix tag } @@ -1408,7 +1408,7 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t if (!fullname.empty()) { json["fullname"] = dv.has_tag() ? fullname + " " + tag_to_string(dv.tag) : fullname; // suffix tag - // TODO check TAG https://github.com/emsesp/EMS-ESP32/issues/1338 + // TAG https://github.com/emsesp/EMS-ESP32/issues/1338 json["fullname"] = dv.has_tag() ? std::string(tag_to_string(dv.tag)) + " " + fullname.c_str() : fullname; // prefix tag } @@ -1614,7 +1614,7 @@ bool EMSdevice::generate_values(JsonObject output, const uint8_t tag_filter, con // add tag if (have_tag) { snprintf(name, sizeof(name), "%s %s (%s)", tag_to_string(dv.tag), fullname.c_str(), dv.short_name); // prefix tag - // TODO check TAG https://github.com/emsesp/EMS-ESP32/issues/1338 + // TAG https://github.com/emsesp/EMS-ESP32/issues/1338 // snprintf(name, sizeof(name), "%s %s (%s)", fullname.c_str(), tag_to_string(dv.tag), dv.short_name); // sufix tag } else { snprintf(name, sizeof(name), "%s (%s)", fullname.c_str(), dv.short_name); diff --git a/src/emsesp.cpp b/src/emsesp.cpp index a576a7f2f..0418a6e4f 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -664,9 +664,10 @@ void EMSESP::publish_response(std::shared_ptr telegram) { buffer = nullptr; } -// builds json with the detail of each value, -// for a specific EMS device type or the sensors, scheduler and custom entities +// builds json with the detail of each value, for an EMS device +// for other types like sensors, scheduler, custom entities it will process single commands like 'info', 'values', 'commands'... bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8_t id, const uint8_t devicetype) { + // check first for EMS devices for (const auto & emsdevice : emsdevices) { if (emsdevice->device_type() == devicetype) { if (emsdevice->get_value_info(root, cmd, id)) { @@ -675,24 +676,24 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8 } } - // specific for the temperaturesensor + // temperaturesensor if (devicetype == DeviceType::TEMPERATURESENSOR) { - return EMSESP::temperaturesensor_.get_value_info(root, cmd, id); + return temperaturesensor_.get_value_info(root, cmd, id); } // analog sensor if (devicetype == DeviceType::ANALOGSENSOR) { - return EMSESP::analogsensor_.get_value_info(root, cmd, id); + return analogsensor_.get_value_info(root, cmd, id); } // scheduler if (devicetype == DeviceType::SCHEDULER) { - return EMSESP::webSchedulerService.get_value_info(root, cmd); + return webSchedulerService.get_value_info(root, cmd); } // custom entities if (devicetype == DeviceType::CUSTOM) { - return EMSESP::webCustomEntityService.get_value_info(root, cmd); + return webCustomEntityService.get_value_info(root, cmd); } char error[100]; @@ -1200,7 +1201,9 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const // Print to LOG showing we've added a new device LOG_INFO("Recognized new %s with deviceID 0x%02X", EMSdevice::device_type_2_device_name(device_type), device_id); - // add command commands for all devices, except for connect, controller and gateway + // add commands 'info', 'commands', 'values', 'entities' for all EMS devices + // and register the MQTT subscribe topic for this device + // except for connect, controller and gateway if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) { return true; } diff --git a/src/system.cpp b/src/system.cpp index 9658b146c..bea8287c8 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -119,15 +119,15 @@ bool System::command_allvalues(const char * value, const int8_t id, JsonObject o emsdevice->generate_values(device_output, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::API_VERBOSE); // use nested for id -1 and 0 } - // Custom entities + // Custom Entities device_output = output["Custom Entities"].to(); EMSESP::webCustomEntityService.get_value_info(device_output, ""); // Sensors device_output = output["Analog Sensors"].to(); - EMSESP::analogsensor_.command_info(nullptr, 0, device_output); + EMSESP::analogsensor_.get_value_info(device_output, "values"); device_output = output["Temperature Sensors"].to(); - EMSESP::temperaturesensor_.command_info(nullptr, 0, device_output); + EMSESP::temperaturesensor_.get_value_info(device_output, "values"); return true; } @@ -1233,23 +1233,23 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node["network"] = "Ethernet"; node["hostname"] = ETH.getHostname(); // node["MAC"] = ETH.macAddress(); - node["IPv4 address"] = uuid::printable_to_string(ETH.localIP()) + "/" + uuid::printable_to_string(ETH.subnetMask()); - node["IPv4 gateway"] = uuid::printable_to_string(ETH.gatewayIP()); - node["IPv4 nameserver"] = uuid::printable_to_string(ETH.dnsIP()); - if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { - node["IPv6 address"] = uuid::printable_to_string(ETH.localIPv6()); - } + // node["IPv4 address"] = uuid::printable_to_string(ETH.localIP()) + "/" + uuid::printable_to_string(ETH.subnetMask()); + // node["IPv4 gateway"] = uuid::printable_to_string(ETH.gatewayIP()); + // node["IPv4 nameserver"] = uuid::printable_to_string(ETH.dnsIP()); + // if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { + // node["IPv6 address"] = uuid::printable_to_string(ETH.localIPv6()); + // } } else if (WiFi.status() == WL_CONNECTED) { node["network"] = "WiFi"; node["hostname"] = WiFi.getHostname(); node["RSSI"] = WiFi.RSSI(); // node["MAC"] = WiFi.macAddress(); - node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask()); - node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP()); - node["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP()); - if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { - node["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6()); - } + // node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask()); + // node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP()); + // node["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP()); + // if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000") { + // node["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6()); + // } } #endif EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { diff --git a/src/telegram.cpp b/src/telegram.cpp index ac3da27c3..83382a457 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -509,8 +509,8 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt } if (operation == Telegram::Operation::TX_RAW) { - if (src != ems_bus_id()) { - operation = Telegram::Operation::NONE; // do not check reply/ack for other ids + if (src != ems_bus_id() || dest == 0) { + operation = Telegram::Operation::NONE; // do not check reply/ack for other ids and broadcasts } else if (dest & 0x80) { operation = Telegram::Operation::TX_READ; EMSESP::set_response_id(type_id); diff --git a/src/temperaturesensor.cpp b/src/temperaturesensor.cpp index 5746885bb..096bb6538 100644 --- a/src/temperaturesensor.cpp +++ b/src/temperaturesensor.cpp @@ -45,24 +45,6 @@ void TemperatureSensor::start() { LOG_INFO("Starting Temperature sensor service"); #endif - // Add API calls - Command::add( - EMSdevice::DeviceType::TEMPERATURESENSOR, - F_(info), - [&](const char * value, const int8_t id, JsonObject output) { return command_info(value, id, output); }, - FL_(info_cmd)); - Command::add( - EMSdevice::DeviceType::TEMPERATURESENSOR, - F_(values), - [&](const char * value, const int8_t id, JsonObject output) { return command_info(value, 0, output); }, - nullptr, - CommandFlag::HIDDEN); // this command is hidden - Command::add( - EMSdevice::DeviceType::TEMPERATURESENSOR, - F_(commands), - [&](const char * value, const int8_t id, JsonObject output) { return command_commands(value, id, output); }, - FL_(commands_cmd)); - char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "%s/#", F_(temperaturesensor)); Mqtt::subscribe(EMSdevice::DeviceType::TEMPERATURESENSOR, topic, nullptr); // use empty function callback @@ -361,68 +343,59 @@ bool TemperatureSensor::updated_values() { return false; } -// list commands -bool TemperatureSensor::command_commands(const char * value, const int8_t id, JsonObject output) { - return Command::list(EMSdevice::DeviceType::TEMPERATURESENSOR, output); -} - -// creates JSON doc from values -// returns true if there are no sensors -bool TemperatureSensor::command_info(const char * value, const int8_t id, JsonObject output) { - if (sensors_.empty()) { - return true; - } - - for (const auto & sensor : sensors_) { - char val[10]; - if (id == -1) { // show number and id, info command - JsonObject dataSensor = output[sensor.name()].to(); - dataSensor["id"] = sensor.id(); - dataSensor["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); - dataSensor["type"] = F_(number); - if (Helpers::hasValue(sensor.temperature_c)) { - dataSensor["temp"] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); - } - } else if (id == 0 && Helpers::hasValue(sensor.temperature_c)) { // values command - output[sensor.name()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); - } else if (Helpers::hasValue(sensor.temperature_c)) { - output[sensor.id()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); - } - } - - return (output.size() > 0); -} - -// called from emsesp.cpp, similar to the emsdevice->get_value_info +// called from emsesp.cpp for commands bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, const int8_t id) { if (sensors_.empty()) { + return true; // no sensors, return true + } + + uint8_t show_all = 0; + if (Helpers::hasValue(cmd)) { + show_all = (strncmp(cmd, F_(info), 4) == 0) ? 1 : (strncmp(cmd, F_(values), 6) == 0) ? 2 : 0; + } + + // see if we're showing all sensors + if (show_all) { + for (const auto & sensor : sensors_) { + if (show_all == 1) { + // info + JsonObject dataSensor = output[sensor.name()].to(); + addSensorJson(dataSensor, sensor); + } else { + // values, shortname version. Also used in 'system allvalues' + if (Helpers::hasValue(sensor.temperature_c)) { + char val[10]; + output[sensor.name()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); + } + } + } return true; } - // make a copy of the string command for parsing - char command_s[30]; - strlcpy(command_s, cmd, sizeof(command_s)); - char * attribute_s = nullptr; - // check specific attribute to fetch instead of the complete record - char * breakp = strchr(command_s, '/'); + // check of it a 'commmands' command + if (Helpers::toLower(cmd) == F_(commands)) { + return Command::list(EMSdevice::DeviceType::TEMPERATURESENSOR, output); + } + + // this is for a specific sensor + // make a copy of the string command for parsing, and lowercase it + char sensor_name[30] = {'\0'}; + char * attribute_s = nullptr; + strlcpy(sensor_name, cmd, sizeof(sensor_name)); + auto sensor_lowercase = Helpers::toLower(sensor_name); + + // check for a specific attribute to fetch instead of the complete record + char * breakp = strchr(sensor_name, '/'); if (breakp) { *breakp = '\0'; attribute_s = breakp + 1; } for (const auto & sensor : sensors_) { - if (Helpers::toLower(command_s) == Helpers::toLower(sensor.name().c_str()) || Helpers::toLower(command_s) == Helpers::toLower(sensor.id().c_str())) { - output["id"] = sensor.id(); - output["name"] = sensor.name(); - if (Helpers::hasValue(sensor.temperature_c)) { - char val[10]; - output["value"] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); - } - - output["type"] = F_(number); - output["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); - output["writeable"] = false; - + // match custom name or sensor ID + if (sensor_lowercase == Helpers::toLower(sensor.name().c_str()) || sensor_lowercase == Helpers::toLower(sensor.id().c_str())) { + // add values + addSensorJson(output, sensor); // if we're filtering on an attribute, go find it if (attribute_s) { if (output.containsKey(attribute_s)) { @@ -432,18 +405,33 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons return true; } else { char error[100]; - snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s); + snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, sensor_name); output.clear(); output["message"] = error; return false; } } - return true; + return true; // found a match, exit } } - return false; + + return false; // not found } +void TemperatureSensor::addSensorJson(JsonObject output, const Sensor & sensor) { + output["id"] = sensor.id(); + output["name"] = sensor.name(); + if (Helpers::hasValue(sensor.temperature_c)) { + char val[10]; + output["value"] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); + } + + output["type"] = F_(number); + output["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); + output["writeable"] = false; +} + + // publish a single sensor to MQTT void TemperatureSensor::publish_sensor(const Sensor & sensor) { if (Mqtt::enabled() && Mqtt::publish_single()) { @@ -640,16 +628,18 @@ bool TemperatureSensor::Sensor::apply_customization() { #if defined(EMSESP_TEST) void TemperatureSensor::test() { // add 2 temperature sensors + // Sensor ID: 01-0203-0405-0607 uint8_t addr[ADDR_LEN] = {1, 2, 3, 4, 5, 6, 7, 8}; sensors_.emplace_back(addr); - // sensors_.back().apply_customization(); + sensors_.back().apply_customization(); sensors_.back().temperature_c = 123; sensors_.back().read = true; publish_sensor(sensors_.back()); // call publish single + // Sensor ID: 0B-0C0D-0E0F-1011 uint8_t addr2[ADDR_LEN] = {11, 12, 13, 14, 15, 16, 17, 18}; sensors_.emplace_back(addr2); - // sensors_.back().apply_customization(); + sensors_.back().apply_customization(); sensors_.back().temperature_c = 456; sensors_.back().read = true; publish_sensor(sensors_.back()); // call publish single diff --git a/src/temperaturesensor.h b/src/temperaturesensor.h index 98d10066f..aad999e26 100644 --- a/src/temperaturesensor.h +++ b/src/temperaturesensor.h @@ -83,7 +83,7 @@ class TemperatureSensor { void publish_values(const bool force); void reload(); bool updated_values(); - bool get_value_info(JsonObject output, const char * cmd, const int8_t id); + bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1); // return back reference to the sensor list, used by other classes std::vector sensors() const { @@ -112,8 +112,6 @@ class TemperatureSensor { bool update(const std::string & id, const std::string & name, int16_t offset); - bool command_info(const char * value, const int8_t id, JsonObject output); - #if defined(EMSESP_TEST) void test(); #endif @@ -154,8 +152,7 @@ class TemperatureSensor { int16_t get_temperature_c(const uint8_t addr[]); uint64_t get_id(const uint8_t addr[]); void remove_ha_topic(const std::string & id); - - bool command_commands(const char * value, const int8_t id, JsonObject output); + void addSensorJson(JsonObject output, const Sensor & sensor); std::vector sensors_; // our list of active sensors diff --git a/src/test/test.cpp b/src/test/test.cpp index 48af75f6f..999f0c7ce 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -55,7 +55,7 @@ bool Test::test(const std::string & cmd, int8_t id1, int8_t id2) { // System::test_set_all_active(true); // uncomment if we want to show all entities and give them fake values add_device(0x08, 123); // Nefit Trendline - add_device(0x18, 157); // Bosch CR100 + add_device(0x18, 157); // RC200/CW100 // add_device(0x10, 158); // RC300 - there's no data here @@ -307,12 +307,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const } if (command == "general") { - shell.printfln("Testing adding a boiler, thermostat and sensors..."); + shell.printfln("Testing adding a boiler, thermostat, all sensors, scheduler and custom entities..."); test("general"); - // add sensors - emsesp::EMSESP::analogsensor_.test(); - emsesp::EMSESP::temperaturesensor_.test(); + // setup fake data + EMSESP::webCustomizationService.test(); // set customizations + EMSESP::temperaturesensor_.test(); // add temperature sensors + EMSESP::webSchedulerService.test(); // add scheduler items + EMSESP::webCustomEntityService.test(); // add custom entities // shell.invoke_command("show devices"); // shell.invoke_command("show values"); @@ -331,16 +333,40 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const } if (command == "custom_entities") { - shell.printfln("custom entities..."); - test("general"); + shell.printfln("Adding custom entities..."); + + // add some dummy entities + EMSESP::webCustomEntityService.test(); #ifdef EMSESP_STANDALONE AsyncWebServerRequest request; request.method(HTTP_GET); request.url("/api/custom"); - request.url("/api/custom/boiler_flowtemp"); - request.url("/api/custom/boiler_flowtemp2"); EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/custom/test_custom"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/custom/test_read_only"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/custom/test_ram"); + EMSESP::webAPIService.webAPIService_get(&request); + shell.invoke_command("call custom info"); + +#endif + ok = true; + } + + if (command == "scheduler") { + shell.printfln("Adding Scheduler items..."); + + // add some dummy entities + EMSESP::webSchedulerService.test(); + +#ifdef EMSESP_STANDALONE + AsyncWebServerRequest request; + request.method(HTTP_GET); + request.url("/api/scheduler"); + EMSESP::webAPIService.webAPIService_get(&request); + shell.invoke_command("call scheduler info"); #endif ok = true; } @@ -574,7 +600,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const test("boiler"); test("thermostat"); - JsonDocument doc; // some absurd high number + JsonDocument doc; for (const auto & emsdevice : EMSESP::emsdevices) { if (emsdevice) { doc.clear(); @@ -718,7 +744,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const shell.printfln("Testing device value rendering"); Mqtt::ha_enabled(true); + // Mqtt::ha_enabled(false); + Mqtt::nested_format(1); + // Mqtt::nested_format(0); + // Mqtt::send_response(false); test("boiler"); @@ -731,8 +761,28 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const if (command == "temperature") { shell.printfln("Testing adding Temperature sensor"); + shell.invoke_command("show commands"); + + // load some EMS data + // test("general"); + emsesp::EMSESP::temperaturesensor_.test(); + + shell.invoke_command("call temperaturesensor"); shell.invoke_command("show values"); + shell.invoke_command("call system allvalues"); + shell.invoke_command("call temperaturesensor info"); + shell.invoke_command("call temperaturesensor values"); + + AsyncWebServerRequest request; + request.method(HTTP_GET); + request.url("/api/temperaturesensor/commands"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/temperaturesensor/info"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/temperaturesensor/01-0203-0405-0607"); + EMSESP::webAPIService.webAPIService_get(&request); + ok = true; } @@ -757,18 +807,37 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const shell.printfln("Testing adding Analog sensor"); Mqtt::ha_enabled(true); // Mqtt::ha_enabled(false); + Mqtt::nested_format(1); // Mqtt::nested_format(0); - emsesp::EMSESP::analogsensor_.test(); - shell.invoke_command("show values"); - // shell.invoke_command("call system publish"); - // shell.invoke_command("show mqtt"); + // Mqtt::send_response(false); - // rename - // bool update(uint8_t id, const std::string & name, int16_t offset, float factor, uint8_t uom, uint8_t type); - EMSESP::analogsensor_.update(36, "analogtest", 2, 0.7, 17, 1); + // load some EMS data + test("general"); + + EMSESP::webCustomizationService.test(); // load the analog sensors + + shell.invoke_command("call analogsensor"); shell.invoke_command("show values"); + shell.invoke_command("call system allvalues"); + shell.invoke_command("call analogsensor info"); + shell.invoke_command("call analogsensor values"); + + AsyncWebServerRequest request; + request.method(HTTP_GET); + request.url("/api/analogsensor/commands"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/analogsensor/info"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/analogsensor/test_analog1"); + request.url("/api/analogsensor/36"); + EMSESP::webAPIService.webAPIService_get(&request); + + // test renaming it + // bool update(uint8_t id, const std::string & name, int16_t offset, float factor, uint8_t uom, uint8_t type); + // EMSESP::analogsensor_.update(36, "test_analog1_new", 2, 0.7, 17, 1); + // shell.invoke_command("show values"); // shell.invoke_command("call system publish"); ok = true; } @@ -784,25 +853,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const ok = true; } - if (command == "custom") { - shell.printfln("Testing custom entities"); + if (command == "customization") { + shell.printfln("Testing customization renaming entity"); Mqtt::ha_enabled(true); // Mqtt::send_response(false); test("thermostat"); + // before // shell.invoke_command("call thermostat seltemp"); // shell.invoke_command("call system publish"); - // toggle mode + // find thermostat for (const auto & emsdevice : EMSESP::emsdevices) { - Serial.print("Custom: "); - Serial.print(emsdevice->device_type_name()); - Serial.print(" uniqueid="); - Serial.println(emsdevice->unique_id()); - - if (emsdevice->unique_id() == 1) { // thermostat + if (emsdevice->unique_id() == 1) { + Serial.println(); + Serial.print("Custom: "); + Serial.print(emsdevice->device_type_name()); + Serial.print(" uniqueid="); + Serial.println(emsdevice->unique_id()); std::string a = "00hc1/seltemp|new name>5<52"; emsdevice->setCustomizationEntity(a); break; diff --git a/src/test/test.h b/src/test/test.h index 83caa6e84..5fbbe38c9 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -27,7 +27,7 @@ namespace emsesp { -#define EMSESP_DEBUG_DEFAULT "general" +// #define EMSESP_DEBUG_DEFAULT "general" // #define EMSESP_DEBUG_DEFAULT "thermostat" // #define EMSESP_DEBUG_DEFAULT "solar" @@ -48,15 +48,16 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "lastcode" // #define EMSESP_DEBUG_DEFAULT "2thermostats" // #define EMSESP_DEBUG_DEFAULT "temperature" -// #define EMSESP_DEBUG_DEFAULT "analog" +#define EMSESP_DEBUG_DEFAULT "analog" // #define EMSESP_DEBUG_DEFAULT "api_values" // #define EMSESP_DEBUG_DEFAULT "mqtt_post" // #define EMSESP_DEBUG_DEFAULT "api_wwmode" -// #define EMSESP_DEBUG_DEFAULT "custom" +// #define EMSESP_DEBUG_DEFAULT "customization" // #define EMSESP_DEBUG_DEFAULT "entity_dump" // #define EMSESP_DEBUG_DEFAULT "memory" // #define EMSESP_DEBUG_DEFAULT "coldshot" // #define EMSESP_DEBUG_DEFAULT "custom_entities" +// #define EMSESP_DEBUG_DEFAULT "scheduler" // #define EMSESP_DEBUG_DEFAULT "heat_exchange" class Test { diff --git a/src/version.h b/src/version.h index 761b8afef..dd42d5039 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.6.5-test.10" +#define EMSESP_APP_VERSION "3.6.5-test.11" diff --git a/src/web/WebAPIService.cpp b/src/web/WebAPIService.cpp index e2e311c01..f997cd5f8 100644 --- a/src/web/WebAPIService.cpp +++ b/src/web/WebAPIService.cpp @@ -152,12 +152,13 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) { #if defined(EMSESP_STANDALONE) Serial.print(COLOR_YELLOW); - Serial.print("web response code: "); - Serial.println(ret_codes[return_code]); + Serial.print("data: "); if (output.size()) { - serializeJsonPretty(output, Serial); + serializeJson(output, Serial); } - Serial.println(); + Serial.print(" (response code "); + Serial.print(ret_codes[return_code]); + Serial.println(")"); Serial.print(COLOR_RESET); #endif } diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index ea0b5ae55..84881310d 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -36,7 +36,7 @@ WebCustomEntityService::WebCustomEntityService(AsyncWebServer * server, FS * fs, // load the settings when the service starts void WebCustomEntityService::begin() { _fsPersistence.readFromFS(); - EMSESP::logger().info("Starting Custom entity service"); + EMSESP::logger().info("Starting Custom Entity service"); Mqtt::subscribe(EMSdevice::DeviceType::CUSTOM, "custom/#", nullptr); // use empty function callback } @@ -61,31 +61,17 @@ void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) { } } -// call on initialization and also when the Entity web page is updated +// call on initialization and also when the Entity web page is updated/saved // this loads the data into the internal class StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & webCustomEntity) { -#ifdef EMSESP_STANDALONE - // invoke some fake data for testing - // clang-format off - /* prettier-ignore */ - const char * json = - "{\"entities\": [{\"id\":0,\"device_id\":8,\"type_id\":24,\"offset\":0,\"factor\":1,\"name\":\"boiler_flowtemp\",\"uom\":1,\"value_type\":1,\"writeable\":true}]}"; - // clang-format on - JsonDocument doc; - deserializeJson(doc, json); - root = doc.as(); - Serial.print(COLOR_BRIGHT_MAGENTA); - Serial.print(" Using fake custom entity file: "); - serializeJson(root, Serial); - Serial.println(COLOR_RESET); -#endif - + // reset everything to start fresh for (CustomEntityItem & entityItem : webCustomEntity.customEntityItems) { Command::erase_command(EMSdevice::DeviceType::CUSTOM, entityItem.name.c_str()); } webCustomEntity.customEntityItems.clear(); EMSESP::webCustomEntityService.ha_reset(); + // rebuild the list if (root["entities"].is()) { for (const JsonObject ei : root["entities"].as()) { auto entityItem = CustomEntityItem(); @@ -120,10 +106,13 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web } else if (entityItem.value_type == DeviceValueType::ULONG || entityItem.value_type == DeviceValueType::TIME) { entityItem.value = EMS_VALUE_DEFAULT_ULONG; } + if (entityItem.factor == 0) { entityItem.factor = 1; } + webCustomEntity.customEntityItems.push_back(entityItem); // add to list + if (entityItem.writeable) { Command::add( EMSdevice::DeviceType::CUSTOM, @@ -262,15 +251,16 @@ void WebCustomEntityService::show_values(JsonObject output) { } } - // process json output for info/commands and value_info bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) { EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; }); + + // if it's commands... if (Helpers::toLower(cmd) == F_(commands)) { output[F_(info)] = Helpers::translated_word(FL_(info_cmd)); output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd)); for (const auto & entity : *customEntityItems) { - output[entity.name] = "custom entitiy"; + output[entity.name] = "custom entity"; } return true; } @@ -281,6 +271,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) return true; } + // if it's info or values... if (strlen(cmd) == 0 || Helpers::toLower(cmd) == F_(values) || Helpers::toLower(cmd) == F_(info)) { // list all names for (const CustomEntityItem & entity : *customEntityItems) { @@ -350,12 +341,14 @@ void WebCustomEntityService::publish_single(const CustomEntityItem & entity) { if (!Mqtt::enabled() || !Mqtt::publish_single()) { return; } + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; if (Mqtt::publish_single2cmd()) { snprintf(topic, sizeof(topic), "%s/%s", "custom", entity.name.c_str()); } else { snprintf(topic, sizeof(topic), "%s/%s", "custom_data", entity.name.c_str()); } + JsonDocument doc; JsonObject output = doc.to(); render_value(output, entity, true); @@ -473,6 +466,7 @@ uint8_t WebCustomEntityService::count_entities() { render_value(output, entity); count += (output.containsKey(entity.name) || entity.writeable) ? 1 : 0; } + return count; } @@ -482,6 +476,7 @@ uint8_t WebCustomEntityService::has_commands() { for (const CustomEntityItem & entity : *customEntityItems) { count += entity.writeable ? 1 : 0; } + return count; } @@ -547,6 +542,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output) { default: break; } + // show only entities with value or command if (!obj.containsKey("v") && !obj.containsKey("c")) { data.remove(index); @@ -560,6 +556,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output) { void WebCustomEntityService::fetch() { EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; }); const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3}; + for (auto & entity : *customEntityItems) { if (entity.device_id > 0 && entity.type_id > 0) { // ths excludes also RAM type bool needFetch = true; @@ -615,11 +612,65 @@ bool WebCustomEntityService::get_value(std::shared_ptr telegram) // EMSESP::logger().debug("custom entity %s received with value %d", entity.name.c_str(), (int)entity.val); } } + if (has_change) { publish(); return true; } + return false; } +// hard coded tests +#ifdef EMSESP_TEST +void WebCustomEntityService::test() { + update( + [&](WebCustomEntity & webCustomEntity) { + webCustomEntity.customEntityItems.clear(); + // test 1 + auto entityItem = CustomEntityItem(); + entityItem.ram = 0; + entityItem.device_id = 8; + entityItem.type_id = 24; + entityItem.offset = 0; + entityItem.factor = 1; + entityItem.name = "test_custom"; + entityItem.uom = 1; + entityItem.value_type = 1; + entityItem.writeable = true; + entityItem.data = "70"; + webCustomEntity.customEntityItems.push_back(entityItem); + + // test 2 + entityItem.ram = 0; + entityItem.device_id = 24; + entityItem.type_id = 677; + entityItem.offset = 3; + entityItem.factor = 1; + entityItem.name = "test_read_only"; + entityItem.uom = 0; + entityItem.value_type = 2; + entityItem.writeable = false; + entityItem.data = "48"; + webCustomEntity.customEntityItems.push_back(entityItem); + + // test 2 + entityItem.ram = 1; + entityItem.device_id = 0; + entityItem.type_id = 0; + entityItem.offset = 0; + entityItem.factor = 1; + entityItem.name = "test_ram"; + entityItem.uom = 0; + entityItem.value_type = 8; + entityItem.writeable = true; + entityItem.data = "14"; + webCustomEntity.customEntityItems.push_back(entityItem); + + return StateUpdateResult::CHANGED; // persist the changes + }, + "local"); +} +#endif + } // namespace emsesp diff --git a/src/web/WebCustomEntityService.h b/src/web/WebCustomEntityService.h index 228cd95c6..ab2cef9ba 100644 --- a/src/web/WebCustomEntityService.h +++ b/src/web/WebCustomEntityService.h @@ -38,7 +38,7 @@ class CustomEntityItem { bool writeable; uint32_t value; std::string data; - uint8_t ram; + uint8_t ram; }; class WebCustomEntity { @@ -70,6 +70,10 @@ class WebCustomEntityService : public StatefulService { ha_registered_ = false; } +#if defined(EMSESP_TEST) + void test(); +#endif + private: HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 61e9f18db..04b6c1b61 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -89,19 +89,6 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root) // call on initialization and also when the page is saved via web UI // this loads the data into the internal class StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & customizations) { -#ifdef EMSESP_STANDALONE - // invoke some fake data for testing - const char * json = "{\"ts\":[],\"as\":[],\"masked_entities\":[{\"product_id\":123,\"device_id\":8,\"entity_ids\":[\"08heatingactive|my custom " - "name for heating active (HS1)\",\"08tapwateractive\"]}]}"; - JsonDocument doc; - deserializeJson(doc, json); - root = doc.as(); - Serial.print(COLOR_BRIGHT_MAGENTA); - Serial.print(" Using fake customization file: "); - serializeJson(root, Serial); - Serial.println(COLOR_RESET); -#endif - // Temperature Sensor customization customizations.sensorCustomizations.clear(); if (root["ts"].is()) { @@ -120,35 +107,36 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c if (root["as"].is()) { for (const JsonObject analogJson : root["as"].as()) { // create each of the sensor, overwriting any previous settings - auto sensor = AnalogCustomization(); - sensor.gpio = analogJson["gpio"]; - sensor.name = analogJson["name"].as(); - sensor.offset = analogJson["offset"]; - sensor.factor = analogJson["factor"]; - sensor.uom = analogJson["uom"]; - sensor.type = analogJson["type"]; - if (_start && sensor.type == EMSESP::analogsensor_.AnalogType::DIGITAL_OUT && sensor.uom > DeviceValue::DeviceValueUOM::NONE) { - sensor.offset = sensor.uom - 1; + auto analog = AnalogCustomization(); + analog.gpio = analogJson["gpio"]; + analog.name = analogJson["name"].as(); + analog.offset = analogJson["offset"]; + analog.factor = analogJson["factor"]; + analog.uom = analogJson["uom"]; + analog.type = analogJson["type"]; + if (_start && analog.type == EMSESP::analogsensor_.AnalogType::DIGITAL_OUT && analog.uom > DeviceValue::DeviceValueUOM::NONE) { + analog.offset = analog.uom - 1; } - customizations.analogCustomizations.push_back(sensor); // add to list + customizations.analogCustomizations.push_back(analog); // add to list } } + _start = false; // load array of entities id's with masks, building up the object class customizations.entityCustomizations.clear(); if (root["masked_entities"].is()) { for (const JsonObject masked_entities : root["masked_entities"].as()) { - auto new_entry = EntityCustomization(); - new_entry.product_id = masked_entities["product_id"]; - new_entry.device_id = masked_entities["device_id"]; + auto emsEntity = EntityCustomization(); + emsEntity.product_id = masked_entities["product_id"]; + emsEntity.device_id = masked_entities["device_id"]; for (const JsonVariant masked_entity_id : masked_entities["entity_ids"].as()) { if (masked_entity_id.is()) { - new_entry.entity_ids.push_back(masked_entity_id.as()); // add entity list + emsEntity.entity_ids.push_back(masked_entity_id.as()); // add entity list } } - customizations.entityCustomizations.push_back(new_entry); // save the new object + customizations.entityCustomizations.push_back(emsEntity); // save the new object } } @@ -256,6 +244,7 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req } // emsesp::EMSESP::logger().info(id.as()); } + // add deleted entities from file read([&](WebCustomization & settings) { for (EntityCustomization entityCustomization : settings.entityCustomizations) { @@ -282,6 +271,7 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req } } }); + // get list of entities that have masks set or a custom fullname emsdevice->getCustomizationEntities(entity_ids); @@ -326,4 +316,61 @@ void WebCustomizationService::begin() { _fsPersistence.readFromFS(); } -} // namespace emsesp \ No newline at end of file + +// hard coded tests +#ifdef EMSESP_TEST +void WebCustomizationService::test() { + update( + [&](WebCustomization & webCustomization) { + // Temperature sensors + webCustomization.sensorCustomizations.clear(); + auto sensor = SensorCustomization(); + sensor.id = "01-0203-0405-0607"; + sensor.name = "test_sensor1"; + sensor.offset = 0; + webCustomization.sensorCustomizations.push_back(sensor); + + sensor = SensorCustomization(); + sensor.id = "0B-0C0D-0E0F-1011"; + sensor.name = "test_sensor2"; + sensor.offset = 4; + webCustomization.sensorCustomizations.push_back(sensor); + + // Analog sensors + // This actually adds the sensors as we use customizations to store them + webCustomization.analogCustomizations.clear(); + auto analog = AnalogCustomization(); + analog.gpio = 36; + analog.name = "test_analog1"; + analog.offset = 0; + analog.factor = 0.1; + analog.uom = 17; + analog.type = 3; + webCustomization.analogCustomizations.push_back(analog); + + analog = AnalogCustomization(); + analog.gpio = 37; + analog.name = "test_analog2"; + analog.offset = 0; + analog.factor = 1; + analog.uom = 0; + analog.type = 1; + webCustomization.analogCustomizations.push_back(analog); + + // EMS entities + webCustomization.entityCustomizations.clear(); + auto emsEntity = EntityCustomization(); + emsEntity.product_id = 123; + emsEntity.device_id = 8; + emsEntity.entity_ids.push_back("08heatingactive|is my heating on?"); + webCustomization.entityCustomizations.push_back(emsEntity); + + return StateUpdateResult::CHANGED; // persist the changes + }, + "local"); + + EMSESP::analogsensor_.reload(); // this is needed to active the analog sensors +} +#endif + +} // namespace emsesp diff --git a/src/web/WebCustomizationService.h b/src/web/WebCustomizationService.h index 8ec322b71..41ceea90c 100644 --- a/src/web/WebCustomizationService.h +++ b/src/web/WebCustomizationService.h @@ -83,6 +83,10 @@ class WebCustomizationService : public StatefulService { void begin(); +#if defined(EMSESP_TEST) + void test(); +#endif + // make all functions public so we can test in the debug and standalone mode #ifndef EMSESP_STANDALONE private: diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index b7fd19d74..2c56164a0 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -17,6 +17,7 @@ */ #include "emsesp.h" +#include "WebSchedulerService.h" namespace emsesp { @@ -30,6 +31,13 @@ WebSchedulerService::WebSchedulerService(AsyncWebServer * server, FS * fs, Secur // load the settings when the service starts void WebSchedulerService::begin() { _fsPersistence.readFromFS(); + + // save a local pointer to the scheduler item list + EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { + // + scheduleItems_ = &webScheduler.scheduleItems; + }); + EMSESP::logger().info("Starting Scheduler service"); Mqtt::subscribe(EMSdevice::DeviceType::SCHEDULER, "scheduler/#", nullptr); // use empty function callback } @@ -54,26 +62,15 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) { // call on initialization and also when the Schedule web page is saved // this loads the data into the internal class StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) { -#ifdef EMSESP_STANDALONE - // invoke some fake data for testing - const char * json = - "{\"schedule\": [{\"id\":1,\"active\":true,\"flags\":31,\"time\": \"07:30\",\"cmd\": \"hc1mode\",\"value\": \"day\",\"name\": \"turn on " - "central heating\"}]}"; - JsonDocument doc; - deserializeJson(doc, json); - root = doc.as(); - Serial.print(COLOR_BRIGHT_MAGENTA); - Serial.print(" Using fake scheduler file: "); - serializeJson(root, Serial); - Serial.println(COLOR_RESET); -#endif - for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) { Command::erase_command(EMSdevice::DeviceType::SCHEDULER, scheduleItem.name.c_str()); } + + // reset the list webScheduler.scheduleItems.clear(); EMSESP::webSchedulerService.ha_reset(); + // build up the list of schedule items if (root["schedule"].is()) { for (const JsonObject schedule : root["schedule"].as()) { // create each schedule item, overwriting any previous settings @@ -103,7 +100,9 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu } } } + EMSESP::webSchedulerService.publish(true); + return StateUpdateResult::CHANGED; } @@ -114,17 +113,19 @@ bool WebSchedulerService::command_setvalue(const char * value, const std::string return false; } - EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; }); - for (ScheduleItem & scheduleItem : *scheduleItems) { + for (ScheduleItem & scheduleItem : *scheduleItems_) { if (scheduleItem.name == name) { if (scheduleItem.active == v) { return true; } + scheduleItem.active = v; publish_single(name.c_str(), v); + if (EMSESP::mqtt_.get_publish_onchange(0)) { publish(); } + return true; } } @@ -133,25 +134,27 @@ bool WebSchedulerService::command_setvalue(const char * value, const std::string // process json output for info/commands and value_info bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { - EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; }); + // check of it a 'commmands' command if (Helpers::toLower(cmd) == F_(commands)) { output[F_(info)] = Helpers::translated_word(FL_(info_cmd)); output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd)); - for (const ScheduleItem & scheduleItem : *scheduleItems) { + + for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (!scheduleItem.name.empty()) { output[scheduleItem.name] = "activate schedule"; } } + return true; } - if (scheduleItems->size() == 0) { + if (scheduleItems_->size() == 0) { return true; } if (strlen(cmd) == 0 || Helpers::toLower(cmd) == F_(values) || Helpers::toLower(cmd) == F_(info)) { // list all names - for (const ScheduleItem & scheduleItem : *scheduleItems) { + for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (!scheduleItem.name.empty()) { if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) { output[scheduleItem.name] = scheduleItem.active; @@ -163,6 +166,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { } } } + return (output.size() > 0); } @@ -177,7 +181,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { attribute_s = breakp + 1; } - for (const ScheduleItem & scheduleItem : *scheduleItems) { + for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (Helpers::toLower(scheduleItem.name) == Helpers::toLower(command_s)) { output["name"] = scheduleItem.name; output["type"] = "boolean"; @@ -238,20 +242,19 @@ void WebSchedulerService::publish(const bool force) { return; } - EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; }); - if (scheduleItems->size() == 0) { + if (scheduleItems_->size() == 0) { return; } if (Mqtt::publish_single() && force) { - for (const ScheduleItem & scheduleItem : *scheduleItems) { + for (const ScheduleItem & scheduleItem : *scheduleItems_) { publish_single(scheduleItem.name.c_str(), scheduleItem.active); } } JsonDocument doc; bool ha_created = ha_registered_; - for (const ScheduleItem & scheduleItem : *scheduleItems) { + for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (!scheduleItem.name.empty() && !doc.containsKey(scheduleItem.name)) { if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) { doc[scheduleItem.name] = scheduleItem.active; @@ -314,16 +317,16 @@ void WebSchedulerService::publish(const bool force) { } bool WebSchedulerService::has_commands() { - EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; }); - if (scheduleItems->size() == 0) { + if (scheduleItems_->size() == 0) { return false; } - for (const ScheduleItem & scheduleItem : *scheduleItems) { + for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (!scheduleItem.name.empty()) { return true; } } + return false; } @@ -375,14 +378,13 @@ void WebSchedulerService::loop() { static uint32_t last_uptime_min = 0; // get list of scheduler events and exit if it's empty - EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; }); - if (scheduleItems->size() == 0) { + if (scheduleItems_->size() == 0) { return; } // check startup commands if (last_tm_min == -1) { - for (ScheduleItem & scheduleItem : *scheduleItems) { + for (ScheduleItem & scheduleItem : *scheduleItems_) { if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) { scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : 0; } @@ -393,7 +395,7 @@ void WebSchedulerService::loop() { // check timer every minute, sync to EMS-ESP clock uint32_t uptime_min = uuid::get_uptime_sec() / 60; if (last_uptime_min != uptime_min) { - for (ScheduleItem & scheduleItem : *scheduleItems) { + for (ScheduleItem & scheduleItem : *scheduleItems_) { // retry startup commands not yet executed if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0 && scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) { @@ -416,7 +418,7 @@ void WebSchedulerService::loop() { uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday uint16_t real_min = tm->tm_hour * 60 + tm->tm_min; - for (const ScheduleItem & scheduleItem : *scheduleItems) { + for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (scheduleItem.active && (real_dow & scheduleItem.flags) && real_min == scheduleItem.elapsed_min) { command(scheduleItem.cmd.c_str(), scheduleItem.value.c_str()); } @@ -425,4 +427,29 @@ void WebSchedulerService::loop() { } } +// hard coded tests +#if defined(EMSESP_TEST) +void WebSchedulerService::test() { + update( + [&](WebScheduler & webScheduler) { + webScheduler.scheduleItems.clear(); + // test 1 + auto si = ScheduleItem(); + si.active = true; + si.flags = 1; + si.time = "12:00"; + si.cmd = "system/fetch"; + si.value = "10"; + si.name = "test_scheduler"; + si.elapsed_min = 0; + si.retry_cnt = 0xFF; // no startup retries + + webScheduler.scheduleItems.push_back(si); + + return StateUpdateResult::CHANGED; // persist the changes + }, + "local"); +} +#endif + } // namespace emsesp diff --git a/src/web/WebSchedulerService.h b/src/web/WebSchedulerService.h index d5496a9e1..4b7e87897 100644 --- a/src/web/WebSchedulerService.h +++ b/src/web/WebSchedulerService.h @@ -62,6 +62,10 @@ class WebSchedulerService : public StatefulService { ha_registered_ = false; } +#if defined(EMSESP_TEST) + void test(); +#endif + // make all functions public so we can test in the debug and standalone mode #ifndef EMSESP_STANDALONE private: @@ -71,7 +75,7 @@ class WebSchedulerService : public StatefulService { HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - std::list * scheduleItems; // pointer to the list of schedule events + std::list * scheduleItems_; // pointer to the list of schedule events bool ha_registered_ = false; }; diff --git a/test/README.txt b/test/README.txt new file mode 100644 index 000000000..7df9ad747 --- /dev/null +++ b/test/README.txt @@ -0,0 +1,6 @@ +This folder contains the default data used when testing in standalone mode. + +It is used for simulation and testing and can be invoked by compiling with -DEMSESP_TEST and from the Console using the command `test general` or via an API call like http://ems-esp.local/api?device=system&cmd=test&data=general + +To run in standalone mode without an ESP32 microcontroller use `make run` or `pio run -e standalone -t exec` + diff --git a/test/standalone_file_export/emsesp_customizations.json b/test/standalone_file_export/emsesp_customizations.json new file mode 100644 index 000000000..1880d6513 --- /dev/null +++ b/test/standalone_file_export/emsesp_customizations.json @@ -0,0 +1,27 @@ +{ + "type": "customizations", + "Customizations": { + "ts": [ + { + "id": "01-0203-0405-0607", + "name": "test_sensor1", + "offset": 0 + }, + { + "id": "0B-0C0D-0E0F-1011", + "name": "test_sensor2", + "offset": 4 + } + ], + "as": [], + "masked_entities": [ + { + "product_id": 123, + "device_id": 8, + "entity_ids": [ + "08heatingactive|is my heating on?" + ] + } + ] + } +} \ No newline at end of file diff --git a/test/standalone_file_export/emsesp_entities.json b/test/standalone_file_export/emsesp_entities.json new file mode 100644 index 000000000..d31abfe07 --- /dev/null +++ b/test/standalone_file_export/emsesp_entities.json @@ -0,0 +1,44 @@ +{ + "type": "entities", + "Entities": { + "entities": [ + { + "id": 0, + "ram": 0, + "device_id": 8, + "type_id": 24, + "offset": 0, + "factor": 1, + "name": "test_custom", + "uom": 1, + "value_type": 1, + "writeable": true + }, + { + "id": 1, + "ram": 0, + "device_id": 24, + "type_id": 677, + "offset": 3, + "factor": 1, + "name": "test_read_only", + "uom": 0, + "value_type": 2, + "writeable": false + }, + { + "id": 2, + "ram": 1, + "device_id": 0, + "type_id": 0, + "offset": 0, + "factor": 1, + "name": "test_ram", + "uom": 0, + "value_type": 8, + "writeable": true, + "value": "14" + } + ] + } +} \ No newline at end of file diff --git a/test/standalone_file_export/emsesp_schedule.json b/test/standalone_file_export/emsesp_schedule.json new file mode 100644 index 000000000..4292ab6a8 --- /dev/null +++ b/test/standalone_file_export/emsesp_schedule.json @@ -0,0 +1,16 @@ +{ + "type": "schedule", + "Schedule": { + "schedule": [ + { + "id": 0, + "active": true, + "flags": 1, + "time": "12:00", + "cmd": "system/fetch", + "value": "10", + "name": "test_scheduler" + } + ] + } +} \ No newline at end of file diff --git a/test/standalone_file_export/emsesp_settings.json b/test/standalone_file_export/emsesp_settings.json new file mode 100644 index 000000000..43eaecc5d --- /dev/null +++ b/test/standalone_file_export/emsesp_settings.json @@ -0,0 +1,128 @@ +{ + "type": "settings", + "System": { + "version": "3.6.5" + }, + "Network": { + "ssid": "fake", + "bssid": "", + "password": "fake", + "hostname": "ems-esp", + "static_ip_config": false, + "enableIPv6": false, + "bandwidth20": false, + "tx_power": 20, + "nosleep": false, + "enableMDNS": true, + "enableCORS": false, + "CORSOrigin": "*" + }, + "AP": { + "provision_mode": 1, + "ssid": "ems-esp", + "password": "ems-esp-neo", + "channel": 1, + "ssid_hidden": false, + "max_clients": 4, + "local_ip": "192.168.4.1", + "gateway_ip": "192.168.4.1", + "subnet_mask": "255.255.255.0" + }, + "MQTT": { + "enabled": true, + "host": "192.168.1.200", + "port": 1883, + "base": "ems-espS", + "username": "fake", + "password": "fake", + "client_id": "ems-esp", + "keep_alive": 60, + "clean_session": false, + "entity_format": 1, + "publish_time_boiler": 10, + "publish_time_thermostat": 10, + "publish_time_solar": 10, + "publish_time_mixer": 10, + "publish_time_other": 10, + "publish_time_sensor": 10, + "publish_time_heartbeat": 60, + "mqtt_qos": 0, + "mqtt_retain": false, + "ha_enabled": true, + "nested_format": 1, + "discovery_prefix": "homeassistant", + "discovery_type": 0, + "publish_single": false, + "publish_single2cmd": false, + "send_response": false + }, + "NTP": { + "enabled": false, + "server": "time.google.com", + "tz_label": "Europe/Amsterdam", + "tz_format": "CET-1CEST,M3.5.0,M10.5.0/3" + }, + "OTA": { + "enabled": false, + "port": 8266, + "password": "ems-esp-neo" + }, + "Security": { + "jwt_secret": "ems-esp-neo", + "users": [ + { + "username": "admin", + "password": "admin", + "admin": true + }, + { + "username": "guest", + "password": "guest", + "admin": false + } + ] + }, + "Settings": { + "version": "3.6.5", + "locale": "en", + "tx_mode": 1, + "ems_bus_id": 11, + "syslog_enabled": false, + "syslog_level": 3, + "trace_raw": false, + "syslog_mark_interval": 0, + "syslog_host": "", + "syslog_port": 514, + "boiler_heatingoff": false, + "shower_timer": false, + "shower_alert": false, + "shower_alert_coldshot": 10, + "shower_alert_trigger": 7, + "rx_gpio": 23, + "tx_gpio": 5, + "dallas_gpio": 18, + "dallas_parasite": false, + "led_gpio": 2, + "hide_led": false, + "low_clock": false, + "telnet_enabled": true, + "notoken_api": false, + "readonly_mode": false, + "analog_enabled": true, + "pbutton_gpio": 0, + "solar_maxflow": 30, + "board_profile": "S32", + "fahrenheit": false, + "bool_format": 1, + "bool_dashboard": 1, + "enum_format": 1, + "weblog_level": 6, + "weblog_buffer": 50, + "weblog_compact": true, + "phy_type": 0, + "eth_power": 0, + "eth_phy_addr": 0, + "eth_clock_mode": 0, + "platform": "ESP32" + } +}