From 85c3baeeac474ac6d0b568c4e93370e0c4939a80 Mon Sep 17 00:00:00 2001 From: GreenWizard2015 Date: Tue, 30 Jan 2024 10:30:54 +0000 Subject: [PATCH 1/6] impl. power level control in UI --- ui/package-lock.json | 168 +++++++++++++++++++++++++ ui/package.json | 1 + ui/public/gauge.png | Bin 0 -> 2859 bytes ui/src/App.js | 2 + ui/src/api/CWaterPumpAPI.js | 5 +- ui/src/api/CWaterPumpAPIImpl.js | 22 +++- ui/src/api/CWaterPumpAPIImpl.test.js | 24 +++- ui/src/components/HoldToPour.js | 22 +++- ui/src/components/PowerLevel.js | 57 +++++++++ ui/src/contexts/WaterPumpAPIContext.js | 3 +- ui/src/store/slices/SystemStatus.js | 6 +- ui/src/store/slices/UI.js | 15 ++- 12 files changed, 310 insertions(+), 15 deletions(-) create mode 100644 ui/public/gauge.png create mode 100644 ui/src/components/PowerLevel.js diff --git a/ui/package-lock.json b/ui/package-lock.json index 56aeacb..aa09c0d 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -17,6 +17,7 @@ "react": "^18.2.0", "react-bootstrap": "^2.9.2", "react-dom": "^18.2.0", + "react-input-slider": "^6.0.1", "react-redux": "^9.0.4", "react-scripts": "5.0.1", "redux-persist": "^6.0.0", @@ -2312,6 +2313,95 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/cache": { + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "dependencies": { + "@emotion/sheet": "0.9.4", + "@emotion/stylis": "0.8.5", + "@emotion/utils": "0.11.3", + "@emotion/weak-memoize": "0.2.5" + } + }, + "node_modules/@emotion/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz", + "integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@emotion/cache": "^10.0.27", + "@emotion/css": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/@emotion/css": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", + "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "dependencies": { + "@emotion/serialize": "^0.11.15", + "@emotion/utils": "0.11.3", + "babel-plugin-emotion": "^10.0.27" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "node_modules/@emotion/serialize": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "dependencies": { + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" + } + }, + "node_modules/@emotion/serialize/node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + }, + "node_modules/@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -5713,6 +5803,61 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/babel-plugin-emotion": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", + "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/serialize": "^0.11.16", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + } + }, + "node_modules/babel-plugin-emotion/node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/babel-plugin-emotion/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/babel-plugin-emotion/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-emotion/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -5808,6 +5953,11 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -8609,6 +8759,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -15170,6 +15325,19 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-input-slider": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/react-input-slider/-/react-input-slider-6.0.1.tgz", + "integrity": "sha512-WASm/k0rVw3cXzvfkoUa4wMnF74SbifeuwSmjPC+zanhV4W9KAmPM+H6yXhIZAjJL4TU+sUw/pgyUyAt/txvIw==", + "dependencies": { + "@babel/runtime": "^7.9.2", + "@emotion/core": "^10.0.14" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/ui/package.json b/ui/package.json index bac5489..a474a8f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -12,6 +12,7 @@ "react": "^18.2.0", "react-bootstrap": "^2.9.2", "react-dom": "^18.2.0", + "react-input-slider": "^6.0.1", "react-redux": "^9.0.4", "react-scripts": "5.0.1", "redux-persist": "^6.0.0", diff --git a/ui/public/gauge.png b/ui/public/gauge.png new file mode 100644 index 0000000000000000000000000000000000000000..a95679ecabce0cb86430d19f3d03784f9efe11e6 GIT binary patch literal 2859 zcmaKu2UJtp7KU#Ep(P+qL(x%?qO=4AQA9zygc2|)!WbY_Q5obxj4~(!QDP7gY#^XP zni5bD8HCWQl%azKX~uy;dI*H-I}UHTe9pYL*1qSQz0d!D_wIA=S_utrXDKQoCjvo` z=n*S3M+kzW!P-KI4~$mU7BDdJ`yRGE3_9>|W?1ZwbVP3oIt5Xa8=z zUfvD7iRCmPn_pO*n40-s1IQbKZ>TvA_j|#{-8XLik9+_J-XE~wKmhi8)He=*fTk7~ zc|Qbaeqnm<)5LTid;B#xy1v1j&VdwgjEy~!H63IwXA-1f1OgxfjeH-#GBsVXJn(e+ z?Sti>IDq0#jx2ueT^w!Vu`77PFPGaQLGfEml|KRxJkvig+1~ZJq3O@kiqU6t1e?)JmXMjCZWLI=|O?+Se|2@rQ^rez_M1qqQ+y-YbNTH~^5IKqAK^ZNz|04S zE7*k=bWU8|~b4#%8PGLEXYhMffFin^9*=+;UOKa-yv?0Vbq<^5WCM86lLg&|g5|zk2zKhSiF#Yz8gDNH|NgskYxb^+6&WXm#RW5QLQa zdcmNiWEoHt3_fCOF33g*i1TYxe=_fdAOX)KW`_xsca!&)+@!s?HXJXt$Eit{?DQy| zHXl3mCcwO8^0wBg(lN*0+KF(pT`*-wtN236hzaM)0RMZY0p}i@9PR2HzwDRl>vR6$ z+eBZo&%)ajL0)-<(Luu(ZS=lN1HYzL`Mh|m;-NyHP|Y{0f?eEUB)-q9KtfD+K3HRT zRNtdk3GNymX(m>`HDaIiIhm2b_Y#)YHVXT$v&XQRPvgb)8n!kg8WlY?tigsz-+OxTBBrZMO&B`wAnQoEHHXV~7ov@7jj1VEN=MaJdBZi~@ zaq?`TAB}&aek0e*;#%DB>UdV3ZqcfSm@s`_f7#lT-l0&?e<=$P3&j|hG+K}eScBT_ z2fyxjZ;cU?rTujD+JC?@Gb7zycJ}-ESP;gGX!9^NDJjXvh{Jr*wttbed(I5rD!(|#dY9h%u3G6(@nL!hw2t4eOThm!1T;41)pdyFE1i_Y4dSn2v@I}S|{RZ5k#B3YKO1khcb!X45yRkm? z8it9oBVN8#74_5P=;Zg)#hkp-=_B_qOmEFRFGi`lgDxy-R7|uTEnnGAnPFTTRbgA? z2ve#O&|=qiZrg)XI;!|!NaA4hO@r-W6SJy+w6=!ooR94|AjleeL$}$C2|gtu&L?G9 z%kA!JYYAO=#p+*e^oA2>#t>y%!Tt>Covu}!2o+4bE)2t)Q>@lYb45C$LNuk>?)GzY za~y(8)9r?t zKh03K&|u>I9a+1wh52xm$jum=Zt7L^xei6tVl}Bsa`<=c6*!VO#!cHHIZkx;phhS% zX}3U|(Yfej@{FsT+(vL*+9cPKLjHW4bV99cT8ogKC~BLuWt?htMx@?b1aWM>dgz{E zZ#v%b=T-9Xf#zm)HS+Ypiw1v9=6ssc0?|3;2gvhLB)^|sD3!H0y zLa4FLlEhmsrtHyLNefb3q|){}ESQe9xiC!c5enL9a!guEJhy%M^(wnu=4 zw-9xyP0|b5&dmK1_+k0xp>_5k?*!?+F?e$bQ;8yZRy0QRhI4|w=UJKqhJC8E3VHfe zj8mc&aje{`k@X>9CPKZpyC*mInOj4ejUvSX*WABNHm4#(*xoY%uhLpp7zxnysd_-1c*nfv|CkGHUfq;-^{K65bT{X8d5Ay*oA=BJxk zh%U8Gm0`uk4pUyj11MH#leMvkT-G{QfAG>`7-y4_Kr{aZ1*yla=`va~5x$C)j_abv zT9kJZM!Q*f&6q|~aV0WOd#tW47=xk>!!f-2+h_xknhYFrCgM0MbeQ|*;KG(r`m?&e z@zayWUA-N>w0df$I(EOD)UQtusP20p=gv#SYgH8*)ZMOYaah+RvrS5m%e*Xq)UVXt z^vICJbrUismj#K@hAP2?nh!FNtHdoKQ@wh>5 zhXU)t9io}C0HtboCJT>2y^?Y9iP~F*w4>G2g-H1BxXEyIGU<0@lWm%~DZ_$y6KrWu z)cM$I6I$YQ@qGwzxF3o0l+cbhEA71Hx<6%vY)0LqWKFtWnkP>Ux9g(x&rpX+(S(wW z^pdN!Yy-gq?I@nWuWMx8uz^Fr{PHX}D+}GF+KJH?C*czmZfQ!^D7nJ4;;52V)*^N` z5`s4(8@9|ZtZ&L@?j|$%OD(}KkaDoObFi03u(yF{kT;kh9jp#k6RW3*-AmBkYk<`? s(A}?&#TsC-W`yH}|1zBY#p{&Mh5v2P6P^bDY9Z)|xt&>_iF?Gq0R|!GWdHyG literal 0 HcmV?d00001 diff --git a/ui/src/App.js b/ui/src/App.js index 5f6949a..9d40e06 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -10,6 +10,7 @@ import SystemControls from './components/SystemControls.js'; import SystemStatusArea from './components/SystemStatusArea.js'; import CurrentOperationInfoArea from './components/CurrentOperationInfoArea.js'; import HoldToPour from './components/HoldToPour.js'; +import PowerLevel from './components/PowerLevel.js'; function App({ isConnected }) { return ( @@ -21,6 +22,7 @@ function App({ isConnected }) { {isConnected ? ( <> + diff --git a/ui/src/api/CWaterPumpAPI.js b/ui/src/api/CWaterPumpAPI.js index 7c297ac..fa1a4c6 100644 --- a/ui/src/api/CWaterPumpAPI.js +++ b/ui/src/api/CWaterPumpAPI.js @@ -29,7 +29,10 @@ class CWaterPumpAPI { }); } - async start(runTimeMs) { return await this._impl.start(runTimeMs); } + async start(runTimeMs, powerLevelInPercents) { + return await this._impl.start(runTimeMs, powerLevelInPercents); + } + async stop() { return await this._impl.stop(); } async status() { return await this._impl.status(); } } diff --git a/ui/src/api/CWaterPumpAPIImpl.js b/ui/src/api/CWaterPumpAPIImpl.js index 995f260..f71e776 100644 --- a/ui/src/api/CWaterPumpAPIImpl.js +++ b/ui/src/api/CWaterPumpAPIImpl.js @@ -11,9 +11,27 @@ class CWaterPumpAPIImpl { return { response, requestTime: end - start }; } - async start(runTimeMs) { + async start(runTimeMs, powerLevelInPercents) { + // basic validation + const isValidTime = Number.isInteger(runTimeMs) && (0 < runTimeMs); + if(!isValidTime) { + throw new Error('Pouring time is not a valid number'); + } + + const isValidPowerLevel = Number.isInteger(powerLevelInPercents) && + (0 < powerLevelInPercents) && (powerLevelInPercents <= 100); + if(!isValidPowerLevel) { + throw new Error('Power level is not a valid number'); + } + ///////////////////////////////////////////////////////////////// const { response: { data }, requestTime } = await this._execute( - async () => await this._client.get('/pour_tea', { params: { milliseconds: runTimeMs } }) + async () => await this._client.get( + '/pour_tea', + { params: { + milliseconds: runTimeMs, + powerLevel: powerLevelInPercents, + }} + ) ); return this.preprocessResponse({ response: data, requestTime }); } diff --git a/ui/src/api/CWaterPumpAPIImpl.test.js b/ui/src/api/CWaterPumpAPIImpl.test.js index 5a61036..5784434 100644 --- a/ui/src/api/CWaterPumpAPIImpl.test.js +++ b/ui/src/api/CWaterPumpAPIImpl.test.js @@ -52,12 +52,30 @@ describe('CWaterPumpAPIImpl', () => { // tests per method describe('start', () => { it('common test cases', async () => { - const T = Math.random() * 1000; - const callback = async (api) => await api.start(T); + const T = Math.floor(Math.random() * 1000); + const P = Math.floor(Math.random() * 99) + 1; + const callback = async (api) => await api.start(T, P); await shouldThrowErrorFromResponse(callback); await shouldRethrowError(callback); await shouldPreprocessResponse(callback); - await shouldBeCalledWith(callback, '/pour_tea', { milliseconds: T }); + await shouldBeCalledWith(callback, '/pour_tea', { milliseconds: T, powerLevel: P}); + }); + + it('should throw error if pouring time is not a valid number', async () => { + const message = 'Pouring time is not a valid number'; + const api = new CWaterPumpAPIImpl({ client: {} }); + await expect(api.start('abc', 100)).rejects.toThrow(message); + await expect(api.start(-1, 100)).rejects.toThrow(message); + await expect(api.start(0, 100)).rejects.toThrow(message); + }); + + it('should throw error if power level is not a valid number', async () => { + const message = 'Power level is not a valid number'; + const api = new CWaterPumpAPIImpl({ client: {} }); + await expect(api.start(100, 'abc')).rejects.toThrow(message); + await expect(api.start(100, -1)).rejects.toThrow(message); + await expect(api.start(100, 0)).rejects.toThrow(message); + await expect(api.start(100, 101)).rejects.toThrow(message); }); }); diff --git a/ui/src/components/HoldToPour.js b/ui/src/components/HoldToPour.js index 87a8914..cc49fee 100644 --- a/ui/src/components/HoldToPour.js +++ b/ui/src/components/HoldToPour.js @@ -65,12 +65,23 @@ export function HoldToPourComponent({ startPump, stopPump, interval }) { } // Helper wrapper to simplify the code in the component -function HoldToPourComponent_withExtras({ pouringTime, startPump, stopPump }) { +function HoldToPourComponent_withExtras({ pouringTime, powerLevel, startPump, stopPump }) { const api = useWaterPumpAPI().API; + // to prevent the callback from changing when the pouringTime or powerLevel changes + const _pouringTime = React.useRef(pouringTime); + React.useEffect(() => { _pouringTime.current = pouringTime; }, [pouringTime]); + + const _powerLevel = React.useRef(powerLevel); + React.useEffect(() => { _powerLevel.current = powerLevel; }, [powerLevel]); const _startPump = React.useCallback( - async () => { await startPump({ api, pouringTime }); }, - [api, startPump, pouringTime] + async () => { + await startPump({ + api, + pouringTime: _pouringTime.current, + powerLevel: _powerLevel.current, + }); + }, [api, startPump, _pouringTime, _powerLevel] ); const _stopPump = React.useCallback( async () => { await stopPump({ api }); }, @@ -88,6 +99,9 @@ function HoldToPourComponent_withExtras({ pouringTime, startPump, stopPump }) { }; export default connect( - state => ({ pouringTime: state.UI.pouringTime }), + state => ({ + pouringTime: state.UI.pouringTime, + powerLevel: state.UI.powerLevelInPercents, + }), { startPump, stopPump } )(HoldToPourComponent_withExtras); \ No newline at end of file diff --git a/ui/src/components/PowerLevel.js b/ui/src/components/PowerLevel.js new file mode 100644 index 0000000..1de3420 --- /dev/null +++ b/ui/src/components/PowerLevel.js @@ -0,0 +1,57 @@ +import React from 'react'; +import { Form, Row, Col } from 'react-bootstrap'; +import Slider from 'react-input-slider'; +import { connect } from 'react-redux'; +import { updatePowerLevel } from '../store/slices/UI'; + +const SLIDER_STYLE = { + track: { + width: '100%', + height: '3rem', + backgroundColor: 'none', + backgroundImage: `url(gauge.png)`, + backgroundSize: '100% 100%', + backgroundRepeat: 'no-repeat', + border: '1px solid blue', + }, + active: { + backgroundColor: 'silver', + opacity: 0.3, + }, + thumb: { + height: '2.5rem', + width: 3, + borderRadius: 0, + backgroundColor: '#000', + cursor: 'pointer', + boxSizing: 'border-box' + } +}; + +function PowerLevel({ powerLevel, onChange }) { + return ( + + + Power Level: + + + onChange(100 - x)} + styles={SLIDER_STYLE} + /> +
+ {powerLevel + '%' } +
+ +
+ ); +} + +export default connect( + (state) => ({ + powerLevel: state.UI.powerLevelInPercents, + }), + { onChange: updatePowerLevel } +)(PowerLevel); \ No newline at end of file diff --git a/ui/src/contexts/WaterPumpAPIContext.js b/ui/src/contexts/WaterPumpAPIContext.js index 9f5ae25..209177b 100644 --- a/ui/src/contexts/WaterPumpAPIContext.js +++ b/ui/src/contexts/WaterPumpAPIContext.js @@ -15,7 +15,8 @@ export function WaterPumpAPIProvider({ children }) { () => new CWaterPumpAPI({ URL: apiHost }), [apiHost] ); - + // TODO: provide also the API methods with binded values from the store + // to simplify the code in the components (HodlToPour and PowerLevel) const value = { API: apiObject, }; return ( diff --git a/ui/src/store/slices/SystemStatus.js b/ui/src/store/slices/SystemStatus.js index a1f4eae..212b469 100644 --- a/ui/src/store/slices/SystemStatus.js +++ b/ui/src/store/slices/SystemStatus.js @@ -18,9 +18,9 @@ function withNotification(action, message) { // Async thunks export const startPump = createAsyncThunk( 'systemStatus/startPump', - withNotification( - async ({ api, pouringTime }) => { - return await api.start(pouringTime); + withNotification( + async ({ api, pouringTime, powerLevel }) => { + return await api.start(pouringTime, powerLevel); }, 'Failed to start pump' ) diff --git a/ui/src/store/slices/UI.js b/ui/src/store/slices/UI.js index b462371..b936cae 100644 --- a/ui/src/store/slices/UI.js +++ b/ui/src/store/slices/UI.js @@ -1,7 +1,17 @@ import { createSlice } from '@reduxjs/toolkit'; +function validatePowerLevel(value) { + if (!Number.isInteger(value)) { + value = 1; + } + value = Math.min(value, 100); + value = Math.max(value, 1); + return value; +} + const INITIAL_STATE = { pouringTime: 1000, + powerLevelInPercents: 100, apiHost: '', }; // slice for system status @@ -15,8 +25,11 @@ export const UISlice = createSlice({ updateAPIHost(state, action) { state.apiHost = action.payload; }, + updatePowerLevel(state, action) { + state.powerLevelInPercents = validatePowerLevel(action.payload); + } }, }); export const actions = UISlice.actions; -export const { updatePouringTime, updateAPIHost } = actions; \ No newline at end of file +export const { updatePouringTime, updateAPIHost, updatePowerLevel } = actions; \ No newline at end of file From 7c0ccc387637787f98df61e6226a739a10180b7d Mon Sep 17 00:00:00 2001 From: GreenWizard Date: Tue, 30 Jan 2024 12:29:31 +0100 Subject: [PATCH 2/6] basic impl. of power control --- .../lib/Arduino/WaterPumpController.cpp | 5 +++-- .../lib/Arduino/WaterPumpController.h | 2 +- .../lib/CommandProcessor/CommandProcessor.cpp | 8 +++++-- .../lib/CommandProcessor/CommandProcessor.h | 2 +- .../WaterPumpScheduler/WaterPumpScheduler.cpp | 4 ++-- .../WaterPumpScheduler/WaterPumpScheduler.h | 2 +- .../tea_poor/lib/interfaces/IWaterPump.h | 2 +- .../lib/interfaces/IWaterPumpSchedulerAPI.h | 2 +- controller/tea_poor/src/main.cpp | 5 ++++- .../test_native/tests/CommandProcessor_test.h | 21 ++++++++++--------- .../tests/WaterPumpScheduler_test.h | 6 +++--- .../test_native/tests/mocks/FakeWaterPump.h | 7 ++++++- .../tests/mocks/FakeWaterPumpSchedulerAPI.h | 8 +++++-- 13 files changed, 46 insertions(+), 28 deletions(-) diff --git a/controller/tea_poor/lib/Arduino/WaterPumpController.cpp b/controller/tea_poor/lib/Arduino/WaterPumpController.cpp index 6aef2a0..e337625 100644 --- a/controller/tea_poor/lib/Arduino/WaterPumpController.cpp +++ b/controller/tea_poor/lib/Arduino/WaterPumpController.cpp @@ -20,10 +20,11 @@ void WaterPumpController::setup() { stop(); } -void WaterPumpController::start() { +void WaterPumpController::start(int powerInPercents) { + const int power = map(powerInPercents, 0, 100, 0, _maxPower); _isRunning = true; digitalWrite(_brakePin, LOW); // release breaks - analogWrite(_powerPin, 255); + analogWrite(_powerPin, power); } void WaterPumpController::stop() { diff --git a/controller/tea_poor/lib/Arduino/WaterPumpController.h b/controller/tea_poor/lib/Arduino/WaterPumpController.h index 2e55cac..2597987 100644 --- a/controller/tea_poor/lib/Arduino/WaterPumpController.h +++ b/controller/tea_poor/lib/Arduino/WaterPumpController.h @@ -14,7 +14,7 @@ class WaterPumpController: public IWaterPump { virtual ~WaterPumpController() override; virtual void setup() override; - virtual void start() override; + virtual void start(int powerInPercents) override; virtual void stop() override; virtual bool isRunning() const override { return _isRunning; } diff --git a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp index a58434e..4b030ac 100644 --- a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp +++ b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp @@ -44,13 +44,17 @@ std::string CommandProcessor::status() { return response.str(); } -std::string CommandProcessor::pour_tea(const char *milliseconds) { +std::string CommandProcessor::pour_tea(const char *milliseconds, const char *power) { if (!isValidIntNumber(milliseconds, _waterPumpSafeThreshold + 1)) { // send error message as JSON return std::string("{ \"error\": \"invalid milliseconds value\" }"); } + if (!isValidIntNumber(power, 101)) { + // send error message as JSON + return std::string("{ \"error\": \"invalid power value\" }"); + } // start pouring tea - _waterPump->start( atoi(milliseconds), _env->time() ); + _waterPump->start( atoi(milliseconds), atoi(power), _env->time() ); return status(); } diff --git a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h index a7770a0..54cbdb7 100644 --- a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h +++ b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h @@ -20,7 +20,7 @@ class CommandProcessor { {} std::string status(); - std::string pour_tea(const char *milliseconds); + std::string pour_tea(const char *milliseconds, const char *power); std::string stop(); private: const int _waterPumpSafeThreshold; diff --git a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp index edf3634..34302d0 100644 --- a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp +++ b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp @@ -12,9 +12,9 @@ void WaterPumpScheduler::setup() { _waterPump->setup(); } -void WaterPumpScheduler::start(unsigned long runTimeMs, unsigned long currentTimeMs) { +void WaterPumpScheduler::start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) { _stopTime = currentTimeMs + runTimeMs; - _waterPump->start(); + _waterPump->start(power); } void WaterPumpScheduler::stop() { diff --git a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h index 4e8aa66..5edae6a 100644 --- a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h +++ b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h @@ -23,7 +23,7 @@ class WaterPumpScheduler : public IWaterPumpSchedulerAPI { void tick(unsigned long currentTimeMs); // Public API - void start(unsigned long runTimeMs, unsigned long currentTimeMs) override; + void start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) override; void stop() override; WaterPumpStatus status() override; }; diff --git a/controller/tea_poor/lib/interfaces/IWaterPump.h b/controller/tea_poor/lib/interfaces/IWaterPump.h index eef9ee9..0ec2d48 100644 --- a/controller/tea_poor/lib/interfaces/IWaterPump.h +++ b/controller/tea_poor/lib/interfaces/IWaterPump.h @@ -8,7 +8,7 @@ class IWaterPump { virtual ~IWaterPump() {} virtual void setup() = 0; - virtual void start() = 0; + virtual void start(int powerInPercents) = 0; virtual void stop() = 0; virtual bool isRunning() const = 0; diff --git a/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h b/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h index b82df7d..272640d 100644 --- a/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h +++ b/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h @@ -22,7 +22,7 @@ class IWaterPumpSchedulerAPI { public: virtual ~IWaterPumpSchedulerAPI() {} virtual void stop() = 0; - virtual void start(unsigned long runTimeMs, unsigned long currentTimeMs) = 0; + virtual void start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) = 0; virtual WaterPumpStatus status() = 0; }; diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp index ad142fe..cf1278d 100644 --- a/controller/tea_poor/src/main.cpp +++ b/controller/tea_poor/src/main.cpp @@ -46,8 +46,11 @@ RemoteControl remoteControl( app.get("/pour_tea", [](Request &req, Response &res) { char milliseconds[64]; req.query("milliseconds", milliseconds, 64); + + char power[64]; + req.query("powerLevel", power, 64); - const auto response = commandProcessor.pour_tea(milliseconds); + const auto response = commandProcessor.pour_tea(milliseconds, power); withExtraHeaders(res); res.print(response.c_str()); }); diff --git a/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h index ad0ff38..a9c069a 100644 --- a/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h +++ b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h @@ -3,6 +3,7 @@ #include "mocks/FakeWaterPumpSchedulerAPI.h" #include "mocks/FakeEnvironment.h" +const auto VALID_POWER = "100"; const auto INVALID_TIME_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds value\" }"; // test that pour_tea() method returns error message if milliseconds: // - greater than threshold @@ -11,10 +12,10 @@ const auto INVALID_TIME_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds val // - not a number TEST(CommandProcessor, pour_tea_invalid_milliseconds) { CommandProcessor commandProcessor(123, nullptr, nullptr); - ASSERT_EQ(commandProcessor.pour_tea("1234"), INVALID_TIME_ERROR_MESSAGE); - ASSERT_EQ(commandProcessor.pour_tea("-1"), INVALID_TIME_ERROR_MESSAGE); - ASSERT_EQ(commandProcessor.pour_tea(""), INVALID_TIME_ERROR_MESSAGE); - ASSERT_EQ(commandProcessor.pour_tea("abc"), INVALID_TIME_ERROR_MESSAGE); + ASSERT_EQ(commandProcessor.pour_tea("1234", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); + ASSERT_EQ(commandProcessor.pour_tea("-1", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); + ASSERT_EQ(commandProcessor.pour_tea("", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); + ASSERT_EQ(commandProcessor.pour_tea("abc", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); } // for simplicity of the UI, we should accept as valid 0 and exactly threshold value @@ -22,9 +23,9 @@ TEST(CommandProcessor, pour_tea_valid_boundary_values) { auto env = std::make_shared(); auto waterPump = std::make_shared(); CommandProcessor commandProcessor(123, env, waterPump); - - ASSERT_NE(commandProcessor.pour_tea("0"), INVALID_TIME_ERROR_MESSAGE); - ASSERT_NE(commandProcessor.pour_tea("123"), INVALID_TIME_ERROR_MESSAGE); + + ASSERT_NE(commandProcessor.pour_tea("0", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); + ASSERT_NE(commandProcessor.pour_tea("123", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); } // test that start pouring tea by calling pour_tea() method and its stops after T milliseconds @@ -33,8 +34,8 @@ TEST(CommandProcessor, pour_tea) { env->time(2343); auto waterPump = std::make_shared(); CommandProcessor commandProcessor(10000, env, waterPump); - const auto response = commandProcessor.pour_tea("1234"); - ASSERT_EQ(waterPump->_log, "start(1234, 2343)\n"); + const auto response = commandProcessor.pour_tea("1234", "23"); + ASSERT_EQ(waterPump->_log, "start(1234, 23, 2343)\n"); } // test that stop() method stops pouring tea @@ -69,7 +70,7 @@ TEST(CommandProcessor, status_running) { auto waterPump = std::make_shared(); CommandProcessor commandProcessor(12345, env, waterPump); - commandProcessor.pour_tea("1123"); + commandProcessor.pour_tea("1123", "100"); env->time(123); waterPump->_status.isRunning = true; diff --git a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h index 138541b..69d4066 100644 --- a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h +++ b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h @@ -11,7 +11,7 @@ TEST(WaterPumpScheduler, test_pump_stops_after_given_time) { waterPumpScheduler.setup(); // start water pump unsigned long currentTimeMs = 0; - waterPumpScheduler.start(runTimeMs, currentTimeMs); + waterPumpScheduler.start(runTimeMs, 1, currentTimeMs); // check status auto status = waterPumpScheduler.status(); ASSERT_TRUE(status.isRunning); @@ -34,14 +34,14 @@ TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_ti waterPumpScheduler.setup(); // start water pump unsigned long currentTimeMs = 0; - waterPumpScheduler.start(1, currentTimeMs); + waterPumpScheduler.start(1, 1, currentTimeMs); currentTimeMs += 1; waterPumpScheduler.tick(currentTimeMs); ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped after given time for(int i = 0; i < 10; i++) { // emulate that pump was started again - fakeWaterPump->start(); + fakeWaterPump->start(1); currentTimeMs += 1000; waterPumpScheduler.tick(currentTimeMs); ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped diff --git a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h index 2dcf154..681e9de 100644 --- a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h +++ b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h @@ -7,12 +7,17 @@ class FakeWaterPump : public IWaterPump { private: bool _isRunning = false; + int _powerInPercents = 0; public: void setup() override { _isRunning = false; } - void start() override { _isRunning = true; } void stop() override { _isRunning = false; } + void start(int powerInPercents) override { + _isRunning = true; + _powerInPercents = powerInPercents; + } bool isRunning() const override { return _isRunning; } + int powerInPercents() const { return _powerInPercents; } }; #endif // FAKE_WATER_PUMP_H \ No newline at end of file diff --git a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h index 896046a..0279fa4 100644 --- a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h +++ b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h @@ -11,8 +11,12 @@ class FakeWaterPumpSchedulerAPI : public IWaterPumpSchedulerAPI { _log += "stop()\n"; } - void start(unsigned long runTimeMs, unsigned long currentTimeMs) override { - _log += "start(" + std::to_string(runTimeMs) + ", " + std::to_string(currentTimeMs) + ")\n"; + void start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) override { + _log += "start(" + + std::to_string(runTimeMs) + ", " + + std::to_string(power) + ", " + + std::to_string(currentTimeMs) + + ")\n"; } WaterPumpStatus status() override { From 34634ebc22223c445b9b38849f3ec91be3054815 Mon Sep 17 00:00:00 2001 From: GreenWizard Date: Tue, 30 Jan 2024 13:19:37 +0100 Subject: [PATCH 3/6] some extra tests --- .../test_native/tests/CommandProcessor_test.h | 16 +++++++++++++++- .../test_native/tests/WaterPumpScheduler_test.h | 16 ++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h index a9c069a..1abc329 100644 --- a/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h +++ b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h @@ -5,6 +5,7 @@ const auto VALID_POWER = "100"; const auto INVALID_TIME_ERROR_MESSAGE = "{ \"error\": \"invalid milliseconds value\" }"; +const auto INVALID_POWER_ERROR_MESSAGE = "{ \"error\": \"invalid power value\" }"; // test that pour_tea() method returns error message if milliseconds: // - greater than threshold // - less than 0 @@ -18,6 +19,19 @@ TEST(CommandProcessor, pour_tea_invalid_milliseconds) { ASSERT_EQ(commandProcessor.pour_tea("abc", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); } +// test that pour_tea() method returns error message if power: +// - greater than 100 +// - less than 0 +// - empty string +// - not a number +TEST(CommandProcessor, pour_tea_invalid_power) { + CommandProcessor commandProcessor(123, nullptr, nullptr); + ASSERT_EQ(commandProcessor.pour_tea("123", "101"), INVALID_POWER_ERROR_MESSAGE); + ASSERT_EQ(commandProcessor.pour_tea("123", "-1"), INVALID_POWER_ERROR_MESSAGE); + ASSERT_EQ(commandProcessor.pour_tea("123", ""), INVALID_POWER_ERROR_MESSAGE); + ASSERT_EQ(commandProcessor.pour_tea("123", "abc"), INVALID_POWER_ERROR_MESSAGE); +} + // for simplicity of the UI, we should accept as valid 0 and exactly threshold value TEST(CommandProcessor, pour_tea_valid_boundary_values) { auto env = std::make_shared(); @@ -28,7 +42,7 @@ TEST(CommandProcessor, pour_tea_valid_boundary_values) { ASSERT_NE(commandProcessor.pour_tea("123", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); } -// test that start pouring tea by calling pour_tea() method and its stops after T milliseconds +// test that start pouring tea by calling pour_tea() method with specified parameters TEST(CommandProcessor, pour_tea) { auto env = std::make_shared(); env->time(2343); diff --git a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h index 69d4066..c14654a 100644 --- a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h +++ b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h @@ -6,12 +6,13 @@ TEST(WaterPumpScheduler, test_pump_stops_after_given_time) { // random time between 1 and 10 seconds const unsigned long runTimeMs = 1000 + (rand() % 10) * 1000; - IWaterPumpPtr fakeWaterPump = std::make_shared(); + const auto fakeWaterPump = std::make_shared(); WaterPumpScheduler waterPumpScheduler(fakeWaterPump); waterPumpScheduler.setup(); // start water pump unsigned long currentTimeMs = 0; waterPumpScheduler.start(runTimeMs, 1, currentTimeMs); + ASSERT_EQ(fakeWaterPump->powerInPercents(), 1); // check status auto status = waterPumpScheduler.status(); ASSERT_TRUE(status.isRunning); @@ -29,7 +30,7 @@ TEST(WaterPumpScheduler, test_pump_stops_after_given_time) { // test that pump is periodically forced to stop after given time TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_time) { - IWaterPumpPtr fakeWaterPump = std::make_shared(); + const auto fakeWaterPump = std::make_shared(); WaterPumpScheduler waterPumpScheduler(fakeWaterPump, 1000); // force stop each 1 second waterPumpScheduler.setup(); // start water pump @@ -42,8 +43,19 @@ TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_ti for(int i = 0; i < 10; i++) { // emulate that pump was started again fakeWaterPump->start(1); + ASSERT_EQ(fakeWaterPump->powerInPercents(), 1); currentTimeMs += 1000; waterPumpScheduler.tick(currentTimeMs); ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped } +} + +// test that pumps power is set to specified value +TEST(WaterPumpScheduler, test_pumps_power_is_set_to_specified_value) { + const auto fakeWaterPump = std::make_shared(); + WaterPumpScheduler waterPumpScheduler(fakeWaterPump); + waterPumpScheduler.setup(); + const int power = 23; + waterPumpScheduler.start(1, power, 0); + ASSERT_EQ(fakeWaterPump->powerInPercents(), power); } \ No newline at end of file From 1168e90e72e5d374028ef5355c14d0b1bae8a3d9 Mon Sep 17 00:00:00 2001 From: GreenWizard Date: Tue, 30 Jan 2024 14:11:57 +0100 Subject: [PATCH 4/6] Use IEnvironment in WaterPumpScheduler to get time, instead of passing it in as a parameter. --- .../lib/CommandProcessor/CommandProcessor.cpp | 2 +- .../WaterPumpScheduler/WaterPumpScheduler.cpp | 10 +++-- .../WaterPumpScheduler/WaterPumpScheduler.h | 11 ++++-- .../lib/interfaces/IWaterPumpSchedulerAPI.h | 2 +- controller/tea_poor/src/main.cpp | 8 ++-- .../test_native/tests/CommandProcessor_test.h | 20 +++++----- .../tests/WaterPumpScheduler_test.h | 38 +++++++++++-------- .../tests/mocks/FakeWaterPumpSchedulerAPI.h | 9 ++++- 8 files changed, 58 insertions(+), 42 deletions(-) diff --git a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp index 4b030ac..a1f128f 100644 --- a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp +++ b/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp @@ -54,7 +54,7 @@ std::string CommandProcessor::pour_tea(const char *milliseconds, const char *pow return std::string("{ \"error\": \"invalid power value\" }"); } // start pouring tea - _waterPump->start( atoi(milliseconds), atoi(power), _env->time() ); + _waterPump->start( atoi(milliseconds), atoi(power) ); return status(); } diff --git a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp index 34302d0..4fe03c1 100644 --- a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp +++ b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp @@ -1,7 +1,8 @@ #include "WaterPumpScheduler.h" -WaterPumpScheduler::WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs) : +WaterPumpScheduler::WaterPumpScheduler(IWaterPumpPtr waterPump, IEnvironmentPtr env, unsigned long forceStopIntervalMs) : _waterPump(waterPump), + _env(env), _forceStopIntervalMs(forceStopIntervalMs) { } @@ -12,8 +13,8 @@ void WaterPumpScheduler::setup() { _waterPump->setup(); } -void WaterPumpScheduler::start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) { - _stopTime = currentTimeMs + runTimeMs; +void WaterPumpScheduler::start(unsigned long runTimeMs, int power) { + _stopTime = _env->time() + runTimeMs; _waterPump->start(power); } @@ -22,7 +23,8 @@ void WaterPumpScheduler::stop() { _stopTime = 0; // a bit of paranoia :) } -void WaterPumpScheduler::tick(unsigned long currentTimeMs) { +void WaterPumpScheduler::tick() { + const auto currentTimeMs = _env->time(); if (_stopTime <= currentTimeMs) { stop(); _stopTime = currentTimeMs + _forceStopIntervalMs; // force stop after X milliseconds diff --git a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h index 5edae6a..a7dee36 100644 --- a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h +++ b/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h @@ -3,6 +3,7 @@ #include #include +#include // This class is responsible for scheduling water pump // It is used to make sure that water pump is running for a limited time @@ -10,20 +11,22 @@ class WaterPumpScheduler : public IWaterPumpSchedulerAPI { private: IWaterPumpPtr _waterPump; + IEnvironmentPtr _env; unsigned long _stopTime = 0; // each X milliseconds will force stop water pump unsigned long _forceStopIntervalMs; public: - WaterPumpScheduler(IWaterPumpPtr waterPump, unsigned long forceStopIntervalMs); - WaterPumpScheduler(IWaterPumpPtr waterPump) : WaterPumpScheduler(waterPump, 1000) {} + WaterPumpScheduler(IWaterPumpPtr waterPump, IEnvironmentPtr env, unsigned long forceStopIntervalMs); + // forceStopIntervalMs is set to 1000ms by default + WaterPumpScheduler(IWaterPumpPtr waterPump, IEnvironmentPtr env) : WaterPumpScheduler(waterPump, env, 1000) {} ~WaterPumpScheduler(); void setup(); // for simplicity and testability we are passing current time as parameter - void tick(unsigned long currentTimeMs); + void tick(); // Public API - void start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) override; + void start(unsigned long runTimeMs, int power) override; void stop() override; WaterPumpStatus status() override; }; diff --git a/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h b/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h index 272640d..01edd3e 100644 --- a/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h +++ b/controller/tea_poor/lib/interfaces/IWaterPumpSchedulerAPI.h @@ -22,7 +22,7 @@ class IWaterPumpSchedulerAPI { public: virtual ~IWaterPumpSchedulerAPI() {} virtual void stop() = 0; - virtual void start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) = 0; + virtual void start(unsigned long runTimeMs, int power) = 0; virtual WaterPumpStatus status() = 0; }; diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp index cf1278d..6bacbd7 100644 --- a/controller/tea_poor/src/main.cpp +++ b/controller/tea_poor/src/main.cpp @@ -9,13 +9,13 @@ #include #include -IEnvironmentPtr env = std::make_shared(); +const IEnvironmentPtr env = std::make_shared(); // Setting up water pump -auto waterPump = std::make_shared( +const auto waterPump = std::make_shared( std::make_shared( WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN - ) + ), env ); // build command processor @@ -76,6 +76,6 @@ void setup() { } void loop() { - waterPump->tick(millis()); + waterPump->tick(); remoteControl.process(); }; \ No newline at end of file diff --git a/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h index 1abc329..67936cc 100644 --- a/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h +++ b/controller/tea_poor/test/test_native/tests/CommandProcessor_test.h @@ -34,8 +34,8 @@ TEST(CommandProcessor, pour_tea_invalid_power) { // for simplicity of the UI, we should accept as valid 0 and exactly threshold value TEST(CommandProcessor, pour_tea_valid_boundary_values) { - auto env = std::make_shared(); - auto waterPump = std::make_shared(); + const auto env = std::make_shared(); + const auto waterPump = std::make_shared(env); CommandProcessor commandProcessor(123, env, waterPump); ASSERT_NE(commandProcessor.pour_tea("0", VALID_POWER), INVALID_TIME_ERROR_MESSAGE); @@ -44,9 +44,9 @@ TEST(CommandProcessor, pour_tea_valid_boundary_values) { // test that start pouring tea by calling pour_tea() method with specified parameters TEST(CommandProcessor, pour_tea) { - auto env = std::make_shared(); + const auto env = std::make_shared(); + const auto waterPump = std::make_shared(env); env->time(2343); - auto waterPump = std::make_shared(); CommandProcessor commandProcessor(10000, env, waterPump); const auto response = commandProcessor.pour_tea("1234", "23"); ASSERT_EQ(waterPump->_log, "start(1234, 23, 2343)\n"); @@ -54,8 +54,8 @@ TEST(CommandProcessor, pour_tea) { // test that stop() method stops pouring tea TEST(CommandProcessor, stop) { - auto env = std::make_shared(); - auto waterPump = std::make_shared(); + const auto env = std::make_shared(); + const auto waterPump = std::make_shared(env); CommandProcessor commandProcessor(123, env, waterPump); const auto response = commandProcessor.stop(); ASSERT_EQ(waterPump->_log, "stop()\n"); @@ -63,8 +63,8 @@ TEST(CommandProcessor, stop) { // test that status() method returns JSON string with water pump status TEST(CommandProcessor, status) { - auto env = std::make_shared(); - auto waterPump = std::make_shared(); + const auto env = std::make_shared(); + const auto waterPump = std::make_shared(env); CommandProcessor commandProcessor(123, env, waterPump); const auto response = commandProcessor.status(); ASSERT_EQ(response, "{" @@ -80,8 +80,8 @@ TEST(CommandProcessor, status) { // test that status() method returns JSON string with actual time left TEST(CommandProcessor, status_running) { - auto env = std::make_shared(); - auto waterPump = std::make_shared(); + const auto env = std::make_shared(); + const auto waterPump = std::make_shared(env); CommandProcessor commandProcessor(12345, env, waterPump); commandProcessor.pour_tea("1123", "100"); diff --git a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h index c14654a..a959bc8 100644 --- a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h +++ b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h @@ -1,51 +1,56 @@ #include #include "mocks/FakeWaterPump.h" +#include "mocks/FakeEnvironment.h" #include // test that pump is stopping after given time TEST(WaterPumpScheduler, test_pump_stops_after_given_time) { // random time between 1 and 10 seconds const unsigned long runTimeMs = 1000 + (rand() % 10) * 1000; + const auto fakeEnvironment = std::make_shared(); const auto fakeWaterPump = std::make_shared(); - WaterPumpScheduler waterPumpScheduler(fakeWaterPump); + WaterPumpScheduler waterPumpScheduler(fakeWaterPump, fakeEnvironment); waterPumpScheduler.setup(); // start water pump - unsigned long currentTimeMs = 0; - waterPumpScheduler.start(runTimeMs, 1, currentTimeMs); + fakeEnvironment->time(0); + waterPumpScheduler.start(runTimeMs, 1); ASSERT_EQ(fakeWaterPump->powerInPercents(), 1); // check status auto status = waterPumpScheduler.status(); ASSERT_TRUE(status.isRunning); ASSERT_EQ(status.stopTime, runTimeMs); - while (currentTimeMs < runTimeMs) { - waterPumpScheduler.tick(currentTimeMs); + while (fakeEnvironment->time() < runTimeMs) { + waterPumpScheduler.tick(); ASSERT_TRUE(fakeWaterPump->isRunning()); - currentTimeMs += 100; + fakeEnvironment->time(fakeEnvironment->time() + 100); } // pump should be stopped after given time - waterPumpScheduler.tick(runTimeMs + 1); + fakeEnvironment->time(runTimeMs + 1); + waterPumpScheduler.tick(); ASSERT_FALSE(fakeWaterPump->isRunning()); } // test that pump is periodically forced to stop after given time TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_time) { const auto fakeWaterPump = std::make_shared(); - WaterPumpScheduler waterPumpScheduler(fakeWaterPump, 1000); // force stop each 1 second + const auto fakeEnvironment = std::make_shared(); + const int T = 1000; // 1 second + WaterPumpScheduler waterPumpScheduler(fakeWaterPump, fakeEnvironment, T); // force stop each T waterPumpScheduler.setup(); // start water pump - unsigned long currentTimeMs = 0; - waterPumpScheduler.start(1, 1, currentTimeMs); - currentTimeMs += 1; - waterPumpScheduler.tick(currentTimeMs); + fakeEnvironment->time(0); + waterPumpScheduler.start(1, 1); + fakeEnvironment->time(1); + waterPumpScheduler.tick(); ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped after given time for(int i = 0; i < 10; i++) { // emulate that pump was started again fakeWaterPump->start(1); ASSERT_EQ(fakeWaterPump->powerInPercents(), 1); - currentTimeMs += 1000; - waterPumpScheduler.tick(currentTimeMs); + fakeEnvironment->time(fakeEnvironment->time() + T); + waterPumpScheduler.tick(); ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped } } @@ -53,9 +58,10 @@ TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_ti // test that pumps power is set to specified value TEST(WaterPumpScheduler, test_pumps_power_is_set_to_specified_value) { const auto fakeWaterPump = std::make_shared(); - WaterPumpScheduler waterPumpScheduler(fakeWaterPump); + const auto fakeEnvironment = std::make_shared(); + WaterPumpScheduler waterPumpScheduler(fakeWaterPump, fakeEnvironment); waterPumpScheduler.setup(); const int power = 23; - waterPumpScheduler.start(1, power, 0); + waterPumpScheduler.start(1, power); ASSERT_EQ(fakeWaterPump->powerInPercents(), power); } \ No newline at end of file diff --git a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h index 0279fa4..94c1dcb 100644 --- a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h +++ b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPumpSchedulerAPI.h @@ -3,19 +3,24 @@ #define FAKE_WATER_PUMP_SCHEDULER_API_H #include +#include #include class FakeWaterPumpSchedulerAPI : public IWaterPumpSchedulerAPI { +private: + const IEnvironmentPtr _env; public: + FakeWaterPumpSchedulerAPI(IEnvironmentPtr env) : _env(env) {} + void stop() override { _log += "stop()\n"; } - void start(unsigned long runTimeMs, int power, unsigned long currentTimeMs) override { + void start(unsigned long runTimeMs, int power) override { _log += "start(" + std::to_string(runTimeMs) + ", " + std::to_string(power) + ", " + - std::to_string(currentTimeMs) + + std::to_string(_env->time()) + ")\n"; } From 68fe43a4a3a05197ba2f67f7338576b3f0fb949b Mon Sep 17 00:00:00 2001 From: GreenWizard Date: Tue, 30 Jan 2024 16:25:19 +0100 Subject: [PATCH 5/6] Reduce the number of folders/libraries in the project, because it is redundant. --- .../tea_poor/lib/{CommandProcessor => Core}/CommandProcessor.cpp | 0 .../tea_poor/lib/{CommandProcessor => Core}/CommandProcessor.h | 0 .../lib/{WaterPumpScheduler => Core}/WaterPumpScheduler.cpp | 0 .../lib/{WaterPumpScheduler => Core}/WaterPumpScheduler.h | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename controller/tea_poor/lib/{CommandProcessor => Core}/CommandProcessor.cpp (100%) rename controller/tea_poor/lib/{CommandProcessor => Core}/CommandProcessor.h (100%) rename controller/tea_poor/lib/{WaterPumpScheduler => Core}/WaterPumpScheduler.cpp (100%) rename controller/tea_poor/lib/{WaterPumpScheduler => Core}/WaterPumpScheduler.h (100%) diff --git a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp b/controller/tea_poor/lib/Core/CommandProcessor.cpp similarity index 100% rename from controller/tea_poor/lib/CommandProcessor/CommandProcessor.cpp rename to controller/tea_poor/lib/Core/CommandProcessor.cpp diff --git a/controller/tea_poor/lib/CommandProcessor/CommandProcessor.h b/controller/tea_poor/lib/Core/CommandProcessor.h similarity index 100% rename from controller/tea_poor/lib/CommandProcessor/CommandProcessor.h rename to controller/tea_poor/lib/Core/CommandProcessor.h diff --git a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp b/controller/tea_poor/lib/Core/WaterPumpScheduler.cpp similarity index 100% rename from controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.cpp rename to controller/tea_poor/lib/Core/WaterPumpScheduler.cpp diff --git a/controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h b/controller/tea_poor/lib/Core/WaterPumpScheduler.h similarity index 100% rename from controller/tea_poor/lib/WaterPumpScheduler/WaterPumpScheduler.h rename to controller/tea_poor/lib/Core/WaterPumpScheduler.h From 632f4239d9a9a6ce6510fcb052d184f627baafa0 Mon Sep 17 00:00:00 2001 From: GreenWizard Date: Tue, 30 Jan 2024 17:13:39 +0100 Subject: [PATCH 6/6] uplift logic from WaterPumpController to AdjustedWaterPump --- .../lib/Arduino/WaterPumpController.cpp | 3 +-- .../lib/Arduino/WaterPumpController.h | 3 +-- .../tea_poor/lib/Core/AdjustedWaterPump.h | 26 +++++++++++++++++++ .../tea_poor/lib/interfaces/IWaterPump.h | 2 +- controller/tea_poor/src/main.cpp | 9 ++++--- controller/tea_poor/test/test_native/main.cpp | 1 + .../tests/AdjustedWaterPump_test.h | 18 +++++++++++++ .../tests/WaterPumpScheduler_test.h | 6 ++--- .../test_native/tests/mocks/FakeWaterPump.h | 8 +++--- 9 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 controller/tea_poor/lib/Core/AdjustedWaterPump.h create mode 100644 controller/tea_poor/test/test_native/tests/AdjustedWaterPump_test.h diff --git a/controller/tea_poor/lib/Arduino/WaterPumpController.cpp b/controller/tea_poor/lib/Arduino/WaterPumpController.cpp index e337625..4717f0d 100644 --- a/controller/tea_poor/lib/Arduino/WaterPumpController.cpp +++ b/controller/tea_poor/lib/Arduino/WaterPumpController.cpp @@ -20,8 +20,7 @@ void WaterPumpController::setup() { stop(); } -void WaterPumpController::start(int powerInPercents) { - const int power = map(powerInPercents, 0, 100, 0, _maxPower); +void WaterPumpController::start(int power) { _isRunning = true; digitalWrite(_brakePin, LOW); // release breaks analogWrite(_powerPin, power); diff --git a/controller/tea_poor/lib/Arduino/WaterPumpController.h b/controller/tea_poor/lib/Arduino/WaterPumpController.h index 2597987..9d492c7 100644 --- a/controller/tea_poor/lib/Arduino/WaterPumpController.h +++ b/controller/tea_poor/lib/Arduino/WaterPumpController.h @@ -7,14 +7,13 @@ class WaterPumpController: public IWaterPump { const int _directionPin; const int _brakePin; const int _powerPin; - const int _maxPower = 255; bool _isRunning = false; public: WaterPumpController(int directionPin, int brakePin, int powerPin); virtual ~WaterPumpController() override; virtual void setup() override; - virtual void start(int powerInPercents) override; + virtual void start(int power) override; virtual void stop() override; virtual bool isRunning() const override { return _isRunning; } diff --git a/controller/tea_poor/lib/Core/AdjustedWaterPump.h b/controller/tea_poor/lib/Core/AdjustedWaterPump.h new file mode 100644 index 0000000..6e2a38c --- /dev/null +++ b/controller/tea_poor/lib/Core/AdjustedWaterPump.h @@ -0,0 +1,26 @@ +#ifndef ADJUSTEDWATERPUMP_H +#define ADJUSTEDWATERPUMP_H +#include +#include + +// lightweight wrapper around IWaterPump +// its purpose is to adjust power value to the range of 0..255, for now +class AdjustedWaterPump: public IWaterPump { +private: + const IWaterPumpPtr _pump; +public: + AdjustedWaterPump(IWaterPumpPtr pump) : _pump(pump) {} + virtual ~AdjustedWaterPump() override {} + + virtual void setup() override { _pump->setup(); } + virtual void stop() override { _pump->stop(); } + virtual bool isRunning() const override { return _pump->isRunning(); } + + virtual void start(int powerInPercents) override { + // convert percents to 0..255 range, using float + const float power = (255.0f / 100.0f) * (float)powerInPercents; + _pump->start(floor(power)); + } +}; + +#endif \ No newline at end of file diff --git a/controller/tea_poor/lib/interfaces/IWaterPump.h b/controller/tea_poor/lib/interfaces/IWaterPump.h index 0ec2d48..251b144 100644 --- a/controller/tea_poor/lib/interfaces/IWaterPump.h +++ b/controller/tea_poor/lib/interfaces/IWaterPump.h @@ -8,7 +8,7 @@ class IWaterPump { virtual ~IWaterPump() {} virtual void setup() = 0; - virtual void start(int powerInPercents) = 0; + virtual void start(int power) = 0; virtual void stop() = 0; virtual bool isRunning() const = 0; diff --git a/controller/tea_poor/src/main.cpp b/controller/tea_poor/src/main.cpp index 6bacbd7..e6bce12 100644 --- a/controller/tea_poor/src/main.cpp +++ b/controller/tea_poor/src/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -9,12 +10,14 @@ #include #include -const IEnvironmentPtr env = std::make_shared(); +const auto env = std::make_shared(); // Setting up water pump const auto waterPump = std::make_shared( - std::make_shared( - WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN + std::make_shared( + std::make_shared( + WATER_PUMP_DIRECTION_PIN, WATER_PUMP_BRAKE_PIN, WATER_PUMP_POWER_PIN + ) ), env ); diff --git a/controller/tea_poor/test/test_native/main.cpp b/controller/tea_poor/test/test_native/main.cpp index b6f09e9..3245894 100644 --- a/controller/tea_poor/test/test_native/main.cpp +++ b/controller/tea_poor/test/test_native/main.cpp @@ -3,6 +3,7 @@ // include tests #include "tests/WaterPumpScheduler_test.h" #include "tests/CommandProcessor_test.h" +#include "tests/AdjustedWaterPump_test.h" int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/controller/tea_poor/test/test_native/tests/AdjustedWaterPump_test.h b/controller/tea_poor/test/test_native/tests/AdjustedWaterPump_test.h new file mode 100644 index 0000000..b84fb82 --- /dev/null +++ b/controller/tea_poor/test/test_native/tests/AdjustedWaterPump_test.h @@ -0,0 +1,18 @@ +#include +#include "mocks/FakeWaterPump.h" +#include + +// test that pumps power passed as percents is converted to 0..255 range +TEST(AdjustedWaterPump, test_pumps_power_passed_as_percents_is_converted_to_0_255_range) { + const auto fakeWaterPump = std::make_shared(); + AdjustedWaterPump adjustedWaterPump(fakeWaterPump); + // list of pairs: (powerInPercents, expectedPower) + const std::vector> tests = { + {0, 0}, {1, 2}, {2, 5}, + {50, 127}, {100, 255} + }; + for(const auto& test: tests) { + adjustedWaterPump.start(test.first); + ASSERT_EQ(fakeWaterPump->power(), test.second); + } +} \ No newline at end of file diff --git a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h index a959bc8..367d83c 100644 --- a/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h +++ b/controller/tea_poor/test/test_native/tests/WaterPumpScheduler_test.h @@ -14,7 +14,7 @@ TEST(WaterPumpScheduler, test_pump_stops_after_given_time) { // start water pump fakeEnvironment->time(0); waterPumpScheduler.start(runTimeMs, 1); - ASSERT_EQ(fakeWaterPump->powerInPercents(), 1); + ASSERT_EQ(fakeWaterPump->power(), 1); // check status auto status = waterPumpScheduler.status(); ASSERT_TRUE(status.isRunning); @@ -48,7 +48,7 @@ TEST(WaterPumpScheduler, test_pump_is_periodically_forced_to_stop_after_given_ti for(int i = 0; i < 10; i++) { // emulate that pump was started again fakeWaterPump->start(1); - ASSERT_EQ(fakeWaterPump->powerInPercents(), 1); + ASSERT_EQ(fakeWaterPump->power(), 1); fakeEnvironment->time(fakeEnvironment->time() + T); waterPumpScheduler.tick(); ASSERT_FALSE(fakeWaterPump->isRunning()); // pump should be stopped @@ -63,5 +63,5 @@ TEST(WaterPumpScheduler, test_pumps_power_is_set_to_specified_value) { waterPumpScheduler.setup(); const int power = 23; waterPumpScheduler.start(1, power); - ASSERT_EQ(fakeWaterPump->powerInPercents(), power); + ASSERT_EQ(fakeWaterPump->power(), power); } \ No newline at end of file diff --git a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h index 681e9de..44972d8 100644 --- a/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h +++ b/controller/tea_poor/test/test_native/tests/mocks/FakeWaterPump.h @@ -7,17 +7,17 @@ class FakeWaterPump : public IWaterPump { private: bool _isRunning = false; - int _powerInPercents = 0; + int _power = 0; public: void setup() override { _isRunning = false; } void stop() override { _isRunning = false; } - void start(int powerInPercents) override { + void start(int power) override { _isRunning = true; - _powerInPercents = powerInPercents; + _power = power; } bool isRunning() const override { return _isRunning; } - int powerInPercents() const { return _powerInPercents; } + int power() const { return _power; } }; #endif // FAKE_WATER_PUMP_H \ No newline at end of file